From 7840fa6d90d1867c72b89c0c80852dac4021ede0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 5 Mar 2025 12:38:01 +0400 Subject: [PATCH] Add context menu for gifts in list. --- Telegram/Resources/langs/lang.strings | 2 + .../boosts/giveaway/giveaway.style | 2 + .../peer_gifts/info_peer_gifts_common.cpp | 6 + .../info/peer_gifts/info_peer_gifts_common.h | 6 + .../peer_gifts/info_peer_gifts_widget.cpp | 43 ++++- .../settings/settings_credits_graphics.cpp | 167 +++++++++++------- .../settings/settings_credits_graphics.h | 18 ++ Telegram/SourceFiles/ui/menu_icons.style | 1 + 8 files changed, 181 insertions(+), 64 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d5abcfbad..e30c963fb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3486,6 +3486,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_transfer_button_for" = "Transfer for {price}"; "lng_gift_transfer_wear" = "Wear"; "lng_gift_transfer_take_off" = "Take Off"; +"lng_gift_menu_show" = "Show"; +"lng_gift_menu_hide" = "Hide"; "lng_gift_wear_title" = "Wear {name}"; "lng_gift_wear_about" = "and get these benefits:"; "lng_gift_wear_badge_title" = "Radiant Badge"; diff --git a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style index 3bf228c9b..afcd0a34e 100644 --- a/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/channel_statistics/boosts/giveaway/giveaway.style @@ -221,6 +221,8 @@ darkGiftShare: icon {{ "menu/share", groupCallMembersFg }}; darkGiftTransfer: icon {{ "chat/input_replace", groupCallMembersFg }}; darkGiftNftWear: icon {{ "menu/nft_wear", groupCallMembersFg }}; darkGiftNftTakeOff: icon {{ "menu/nft_takeoff", groupCallMembersFg }}; +darkGiftHide: icon {{ "menu/stealth", groupCallMembersFg }}; +darkGiftShow: icon {{ "menu/show_in_chat", groupCallMembersFg }}; darkGiftPalette: TextPalette(defaultTextPalette) { linkFg: mediaviewTextLinkFg; monoFg: groupCallMembersFg; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index dfd323656..c48cb6f45 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -250,6 +250,12 @@ void GiftButton::resizeEvent(QResizeEvent *e) { } } +void GiftButton::contextMenuEvent(QContextMenuEvent *e) { + _contextMenuRequests.fire_copy((e->reason() == QContextMenuEvent::Mouse) + ? e->globalPos() + : QCursor::pos()); +} + void GiftButton::cacheUniqueBackground( not_null unique, int width, diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index d7d829dab..02c4223b8 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -126,9 +126,14 @@ public: void setDescriptor(const GiftDescriptor &descriptor, Mode mode); void setGeometry(QRect inner, QMargins extend); + [[nodiscard]] rpl::producer contextMenuRequests() const { + return _contextMenuRequests.events(); + } + private: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; void cacheUniqueBackground( not_null unique, @@ -141,6 +146,7 @@ private: void unsubscribe(); const not_null _delegate; + rpl::event_stream _contextMenuRequests; QImage _hiddenBgCache; GiftDescriptor _descriptor; Ui::Text::String _text; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 898125cf3..63f48a7a7 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_premium.h" #include "apiwrap.h" #include "data/data_channel.h" +#include "data/data_credits.h" #include "data/data_session.h" #include "data/data_user.h" #include "info/peer_gifts/info_peer_gifts_common.h" @@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/box_content_divider.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" +#include "ui/widgets/popup_menu.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/slide_wrap.h" #include "ui/ui_utility.h" @@ -99,6 +101,7 @@ private: void refreshButtons(); void validateButtons(); void showGift(int index); + void showMenuFor(not_null button, QPoint point); void refreshAbout(); int resizeGetHeight(int width) override; @@ -131,6 +134,8 @@ private: int _visibleFrom = 0; int _visibleTill = 0; + base::unique_qptr _menu; + }; InnerWidget::InnerWidget( @@ -355,7 +360,12 @@ void InnerWidget::validateButtons() { views.push_back(base::take(*unused)); } else { auto button = std::make_unique(this, &_delegate); - button->show(); + const auto raw = button.get(); + raw->contextMenuRequests( + ) | rpl::start_with_next([=](QPoint point) { + showMenuFor(raw, point); + }, raw->lifetime()); + raw->show(); views.push_back({ .button = std::move(button) }); } } @@ -386,6 +396,37 @@ void InnerWidget::validateButtons() { std::swap(_views, views); } +void InnerWidget::showMenuFor(not_null button, QPoint point) { + if (_menu) { + return; + } + const auto index = [&] { + for (const auto &view : _views) { + if (view.button.get() == button) { + return view.index; + } + } + return -1; + }(); + if (index < 0) { + return; + } + + const auto entry = ::Settings::SavedStarGiftEntry( + _peer, + _entries[index].gift); + _menu = base::make_unique_q(this, st::popupMenuWithIcons); + ::Settings::FillSavedStarGiftMenu( + _controller->uiShow(), + _menu.get(), + entry, + ::Settings::SavedStarGiftMenuType::List); + if (_menu->empty()) { + return; + } + _menu->popup(point); +} + void InnerWidget::showGift(int index) { Expects(index >= 0 && index < _entries.size()); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index d42f40ee8..d680e7ef4 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -189,7 +189,7 @@ void ToggleStarGiftSaved( std::shared_ptr show, Data::SavedStarGiftId savedId, bool save, - Fn done) { + Fn done = nullptr) { using Flag = MTPpayments_SaveStarGift::Flag; const auto api = &show->session().api(); const auto channelGift = savedId.chat(); @@ -197,7 +197,15 @@ void ToggleStarGiftSaved( MTP_flags(save ? Flag(0) : Flag::f_unsave), Api::InputSavedStarGiftId(savedId) )).done([=] { - done(true); + using GiftAction = Data::GiftUpdate::Action; + show->session().data().notifyGiftUpdate({ + .id = savedId, + .action = (save ? GiftAction::Save : GiftAction::Unsave), + }); + + if (const auto onstack = done) { + onstack(true); + } show->showToast((save ? (channelGift ? tr::lng_gift_display_done_channel @@ -206,7 +214,9 @@ void ToggleStarGiftSaved( ? tr::lng_gift_display_done_hide_channel : tr::lng_gift_display_done_hide))(tr::now)); }).fail([=](const MTP::Error &error) { - done(false); + if (const auto onstack = done) { + onstack(false); + } show->showToast(error.type()); }).send(); } @@ -849,26 +859,48 @@ void FillUniqueGiftMenu( std::shared_ptr show, not_null menu, const Data::CreditsHistoryEntry &e, + SavedStarGiftMenuType type, CreditsEntryBoxStyleOverrides st) { - Expects(e.uniqueGift != nullptr); - const auto unique = e.uniqueGift; - const auto local = u"nft/"_q + unique->slug; - const auto url = show->session().createInternalLinkFull(local); - menu->addAction(tr::lng_context_copy_link(tr::now), [=] { - TextUtilities::SetClipboardText({ url }); - show->showToast(tr::lng_channel_public_link_copied(tr::now)); - }, st.link ? st.link : &st::menuIconLink); + if (unique) { + const auto local = u"nft/"_q + unique->slug; + const auto url = show->session().createInternalLinkFull(local); + menu->addAction(tr::lng_context_copy_link(tr::now), [=] { + TextUtilities::SetClipboardText({ url }); + show->showToast(tr::lng_channel_public_link_copied(tr::now)); + }, st.link ? st.link : &st::menuIconLink); - const auto shareBoxSt = st.shareBox; - menu->addAction(tr::lng_chat_link_share(tr::now), [=] { - FastShareLink( - show, - url, - shareBoxSt ? *shareBoxSt : ShareBoxStyleOverrides()); - }, st.share ? st.share : &st::menuIconShare); + const auto shareBoxSt = st.shareBox; + menu->addAction(tr::lng_chat_link_share(tr::now), [=] { + FastShareLink( + show, + url, + shareBoxSt ? *shareBoxSt : ShareBoxStyleOverrides()); + }, st.share ? st.share : &st::menuIconShare); + } const auto savedId = EntryToSavedStarGiftId(&show->session(), e); + const auto giftChannel = savedId.chat(); + const auto canToggleVisibility = savedId + && e.id.isEmpty() + && (e.in || (giftChannel && giftChannel->canManageGifts())) + && !e.giftTransferred + && !e.giftRefunded; + if (canToggleVisibility && type == SavedStarGiftMenuType::List) { + if (e.savedToProfile) { + menu->addAction(tr::lng_gift_menu_hide(tr::now), [=] { + ToggleStarGiftSaved(show, savedId, false); + }, st.hide ? st.hide : &st::menuIconStealth); + } else { + menu->addAction(tr::lng_gift_menu_show(tr::now), [=] { + ToggleStarGiftSaved(show, savedId, true); + }, st.show ? st.show : &st::menuIconShowInChat); + } + } + + if (!unique) { + return; + } const auto transfer = savedId && (savedId.isUser() ? e.in : savedId.chat()->canTransferGifts()) && (unique->starsForTransfer >= 0); @@ -924,6 +956,8 @@ CreditsEntryBoxStyleOverrides DarkCreditsEntryBoxStyle() { .transfer = &st::darkGiftTransfer, .wear = &st::darkGiftNftWear, .takeoff = &st::darkGiftNftTakeOff, + .show = &st::darkGiftShow, + .hide = &st::darkGiftHide, .shareBox = std::make_shared( DarkShareBoxStyle()), .giftWearBox = std::make_shared( @@ -1029,7 +1063,8 @@ void GenericCreditsEntryBox( AddSkip(content, st::defaultVerticalListSkip * 2); AddUniqueCloseButton(box, st, [=](not_null menu) { - FillUniqueGiftMenu(show, menu, e, st); + const auto type = SavedStarGiftMenuType::View; + FillUniqueGiftMenu(show, menu, e, type, st); }); } else if (const auto callback = Ui::PaintPreviewCallback(session, e)) { const auto thumb = content->add(object_ptr>( @@ -1464,21 +1499,12 @@ void GenericCreditsEntryBox( const auto showSection = !e.fromGiftsList; const auto savedId = EntryToSavedStarGiftId(&show->session(), e); const auto done = [=](bool ok) { - if (ok) { - using GiftAction = Data::GiftUpdate::Action; - show->session().data().notifyGiftUpdate({ - .id = savedId, - .action = (save - ? GiftAction::Save - : GiftAction::Unsave), - }); - if (showSection) { - if (const auto window = show->resolveWindow()) { - window->showSection( - std::make_shared( - window->session().user(), - Info::Section::Type::PeerGifts)); - } + if (ok && showSection) { + if (const auto window = show->resolveWindow()) { + window->showSection( + std::make_shared( + window->session().user(), + Info::Section::Type::PeerGifts)); } } if (const auto strong = weak.data()) { @@ -1899,46 +1925,61 @@ void GlobalStarGiftBox( st); } +Data::CreditsHistoryEntry SavedStarGiftEntry( + not_null owner, + const Data::SavedStarGift &data) { + const auto chatGiftPeer = data.manageId.chat(); + return { + .description = data.message, + .date = base::unixtime::parse(data.date), + .credits = StarsAmount(data.info.stars), + .bareMsgId = uint64(data.manageId.userMessageId().bare), + .barePeerId = data.fromId.value, + .bareGiftStickerId = data.info.document->id, + .bareGiftOwnerId = owner->id.value, + .bareActorId = data.fromId.value, + .bareEntryOwnerId = chatGiftPeer ? chatGiftPeer->id.value : 0, + .giftChannelSavedId = data.manageId.chatSavedId(), + .stargiftId = data.info.id, + .uniqueGift = data.info.unique, + .peerType = Data::CreditsHistoryEntry::PeerType::Peer, + .limitedCount = data.info.limitedCount, + .limitedLeft = data.info.limitedLeft, + .starsConverted = int(data.starsConverted), + .starsToUpgrade = int(data.info.starsToUpgrade), + .starsUpgradedBySender = int(data.starsUpgradedBySender), + .converted = false, + .anonymous = data.anonymous, + .stargift = true, + .savedToProfile = !data.hidden, + .fromGiftsList = true, + .canUpgradeGift = data.upgradable, + .in = data.mine, + .gift = true, + }; +} + void SavedStarGiftBox( not_null box, not_null controller, not_null owner, const Data::SavedStarGift &data) { - const auto chatGiftPeer = data.manageId.chat(); Settings::ReceiptCreditsBox( box, controller, - Data::CreditsHistoryEntry{ - .description = data.message, - .date = base::unixtime::parse(data.date), - .credits = StarsAmount(data.info.stars), - .bareMsgId = uint64(data.manageId.userMessageId().bare), - .barePeerId = data.fromId.value, - .bareGiftStickerId = data.info.document->id, - .bareGiftOwnerId = owner->id.value, - .bareActorId = data.fromId.value, - .bareEntryOwnerId = chatGiftPeer ? chatGiftPeer->id.value : 0, - .giftChannelSavedId = data.manageId.chatSavedId(), - .stargiftId = data.info.id, - .uniqueGift = data.info.unique, - .peerType = Data::CreditsHistoryEntry::PeerType::Peer, - .limitedCount = data.info.limitedCount, - .limitedLeft = data.info.limitedLeft, - .starsConverted = int(data.starsConverted), - .starsToUpgrade = int(data.info.starsToUpgrade), - .starsUpgradedBySender = int(data.starsUpgradedBySender), - .converted = false, - .anonymous = data.anonymous, - .stargift = true, - .savedToProfile = !data.hidden, - .fromGiftsList = true, - .canUpgradeGift = data.upgradable, - .in = data.mine, - .gift = true, - }, + SavedStarGiftEntry(owner, data), Data::SubscriptionEntry()); } +void FillSavedStarGiftMenu( + std::shared_ptr show, + not_null menu, + const Data::CreditsHistoryEntry &e, + SavedStarGiftMenuType type, + CreditsEntryBoxStyleOverrides st) { + FillUniqueGiftMenu(show, menu, e, type, st); +} + void StarGiftViewBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 59084cb36..0ea66f359 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -52,6 +52,7 @@ namespace Ui { class GenericBox; class RpWidget; class VerticalLayout; +class PopupMenu; } // namespace Ui namespace Settings { @@ -112,6 +113,8 @@ struct CreditsEntryBoxStyleOverrides { const style::icon *transfer = nullptr; const style::icon *wear = nullptr; const style::icon *takeoff = nullptr; + const style::icon *show = nullptr; + const style::icon *hide = nullptr; std::shared_ptr shareBox; std::shared_ptr giftWearBox; }; @@ -149,11 +152,26 @@ void GlobalStarGiftBox( std::shared_ptr show, const Data::StarGift &data, CreditsEntryBoxStyleOverrides st = {}); + +[[nodiscard]] Data::CreditsHistoryEntry SavedStarGiftEntry( + not_null owner, + const Data::SavedStarGift &data); void SavedStarGiftBox( not_null box, not_null controller, not_null owner, const Data::SavedStarGift &data); +enum class SavedStarGiftMenuType { + List, + View, +}; +void FillSavedStarGiftMenu( + std::shared_ptr show, + not_null menu, + const Data::CreditsHistoryEntry &e, + SavedStarGiftMenuType type, + CreditsEntryBoxStyleOverrides st = {}); + void StarGiftViewBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 504065677..f010afa62 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -23,6 +23,7 @@ menuIconStickers: icon {{ "menu/stickers", menuIconColor }}; menuIconEmoji: icon {{ "menu/emoji", menuIconColor }}; menuIconCancel: icon {{ "menu/cancel", menuIconColor }}; menuIconShowInChat: icon {{ "menu/show_in_chat", menuIconColor }}; +menuIconStealth: icon {{ "menu/stealth", menuIconColor }}; menuIconGif: icon {{ "menu/gif", menuIconColor }}; menuIconShowInFolder: icon {{ "menu/show_in_folder", menuIconColor }}; menuIconDownload: icon {{ "menu/download", menuIconColor }};