Add context menu for gifts in list.

This commit is contained in:
John Preston 2025-03-05 12:38:01 +04:00
parent 95ccc99fee
commit 7840fa6d90
8 changed files with 181 additions and 64 deletions

View file

@ -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";

View file

@ -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;

View file

@ -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<Data::UniqueGift*> unique,
int width,

View file

@ -126,9 +126,14 @@ public:
void setDescriptor(const GiftDescriptor &descriptor, Mode mode);
void setGeometry(QRect inner, QMargins extend);
[[nodiscard]] rpl::producer<QPoint> 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<Data::UniqueGift*> unique,
@ -141,6 +146,7 @@ private:
void unsubscribe();
const not_null<GiftButtonDelegate*> _delegate;
rpl::event_stream<QPoint> _contextMenuRequests;
QImage _hiddenBgCache;
GiftDescriptor _descriptor;
Ui::Text::String _text;

View file

@ -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<GiftButton*> button, QPoint point);
void refreshAbout();
int resizeGetHeight(int width) override;
@ -131,6 +134,8 @@ private:
int _visibleFrom = 0;
int _visibleTill = 0;
base::unique_qptr<Ui::PopupMenu> _menu;
};
InnerWidget::InnerWidget(
@ -355,7 +360,12 @@ void InnerWidget::validateButtons() {
views.push_back(base::take(*unused));
} else {
auto button = std::make_unique<GiftButton>(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<GiftButton*> 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<Ui::PopupMenu>(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());

View file

@ -189,7 +189,7 @@ void ToggleStarGiftSaved(
std::shared_ptr<ChatHelpers::Show> show,
Data::SavedStarGiftId savedId,
bool save,
Fn<void(bool)> done) {
Fn<void(bool)> 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<ChatHelpers::Show> show,
not_null<Ui::PopupMenu*> 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<ShareBoxStyleOverrides>(
DarkShareBoxStyle()),
.giftWearBox = std::make_shared<GiftWearBoxStyleOverride>(
@ -1029,7 +1063,8 @@ void GenericCreditsEntryBox(
AddSkip(content, st::defaultVerticalListSkip * 2);
AddUniqueCloseButton(box, st, [=](not_null<Ui::PopupMenu*> 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<Ui::CenterWrap<>>(
@ -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<Info::Memento>(
window->session().user(),
Info::Section::Type::PeerGifts));
}
if (ok && showSection) {
if (const auto window = show->resolveWindow()) {
window->showSection(
std::make_shared<Info::Memento>(
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<PeerData*> 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<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<PeerData*> 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<ChatHelpers::Show> show,
not_null<Ui::PopupMenu*> menu,
const Data::CreditsHistoryEntry &e,
SavedStarGiftMenuType type,
CreditsEntryBoxStyleOverrides st) {
FillUniqueGiftMenu(show, menu, e, type, st);
}
void StarGiftViewBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,

View file

@ -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<ShareBoxStyleOverrides> shareBox;
std::shared_ptr<GiftWearBoxStyleOverride> giftWearBox;
};
@ -149,11 +152,26 @@ void GlobalStarGiftBox(
std::shared_ptr<ChatHelpers::Show> show,
const Data::StarGift &data,
CreditsEntryBoxStyleOverrides st = {});
[[nodiscard]] Data::CreditsHistoryEntry SavedStarGiftEntry(
not_null<PeerData*> owner,
const Data::SavedStarGift &data);
void SavedStarGiftBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<PeerData*> owner,
const Data::SavedStarGift &data);
enum class SavedStarGiftMenuType {
List,
View,
};
void FillSavedStarGiftMenu(
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::PopupMenu*> menu,
const Data::CreditsHistoryEntry &e,
SavedStarGiftMenuType type,
CreditsEntryBoxStyleOverrides st = {});
void StarGiftViewBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,

View file

@ -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 }};