Implement "Choose gift to unpin" box feature.

This commit is contained in:
John Preston 2025-03-19 18:25:50 +04:00
parent bb8ecf2f84
commit 46e7b6d6df
10 changed files with 419 additions and 107 deletions

View file

@ -3429,7 +3429,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
"lng_gift_pinned_done_title" = "{gift} pinned";
"lng_gift_pinned_done" = "The gift will always be shown on top.";
"lng_gift_pinned_done_replaced" = "replacing {gift}";
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift.";
@ -3502,6 +3504,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles.";
"lng_gift_wear_start_toast" = "You put on {name}";
"lng_gift_wear_end_toast" = "You took off {name}";
"lng_gift_many_pinned_title" = "Too Many Pinned Gifts";
"lng_gift_many_pinned_choose" = "Select a gift to unpin below";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";

View file

@ -883,6 +883,7 @@ std::optional<Data::SavedStarGift> FromTL(
unique->exportAt = data.vcan_export_at().value_or_empty();
}
using Id = Data::SavedStarGiftId;
const auto hasUnique = parsed->unique != nullptr;
return Data::SavedStarGift{
.info = std::move(*parsed),
.manageId = (to->isUser()
@ -905,7 +906,7 @@ std::optional<Data::SavedStarGift> FromTL(
.date = data.vdate().v,
.upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(),
.pinned = data.is_pinned_to_top(),
.pinned = data.is_pinned_to_top() && hasUnique,
.hidden = data.is_unsaved(),
.mine = to->isSelf(),
};

View file

@ -1725,7 +1725,7 @@ void SendGiftBox(
bool sending = false;
};
const auto state = raw->lifetime().make_state<State>(State{
.delegate = Delegate(window, GiftButtonMode::Full),
.delegate = Delegate(&window->session(), GiftButtonMode::Full),
});
const auto single = state->delegate.buttonSize();
const auto shadow = st::defaultDropdownMenu.wrap.shadow;

View file

@ -479,7 +479,8 @@ ClickHandlerPtr OpenStarGiftLink(not_null<HistoryItem*> item) {
Settings::SavedStarGiftBox,
window,
owner,
*parsed));
*parsed,
nullptr));
}
}
}).fail([=](const MTP::Error &error) {

View file

@ -7,20 +7,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/peer_gifts/info_peer_gifts_common.h"
#include "boxes/send_credits_box.h" // SetButtonMarkedLabel
#include "boxes/star_gift_box.h"
#include "boxes/sticker_set_box.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_credits.h" // CreditsHistoryEntry
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_session.h"
#include "history/view/media/history_view_sticker_player.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_credits_graphics.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/effects/premium_graphics.h"
@ -240,6 +245,76 @@ void GiftButton::setGeometry(QRect inner, QMargins extend) {
AbstractButton::setGeometry(inner.marginsAdded(extend));
}
QMargins GiftButton::currentExtend() const {
const auto progress = _selectedAnimation.value(_selected ? 1. : 0.);
const auto added = anim::interpolate(0, st::giftBoxSelectSkip, progress);
return _extend + QMargins(added, added, added, added);
}
void GiftButton::toggleSelected(bool selected) {
if (_selected == selected) {
return;
}
const auto duration = st::defaultRoundCheckbox.duration;
_selected = selected;
_selectedAnimation.start([=] {
update();
}, selected ? 0. : 1., selected ? 1. : 0., duration, anim::easeOutCirc);
}
void GiftButton::paintBackground(QPainter &p, const QImage &background) {
const auto removed = currentExtend() - _extend;
const auto x = removed.left();
const auto y = removed.top();
const auto width = this->width() - x - removed.right();
const auto height = this->height() - y - removed.bottom();
const auto dpr = int(background.devicePixelRatio());
const auto bwidth = background.width() / dpr;
const auto bheight = background.height() / dpr;
const auto fillRow = [&](int yfrom, int ytill, int bfrom) {
const auto fill = [&](int xto, int wto, int xfrom, int wfrom = 0) {
const auto fheight = ytill - yfrom;
p.drawImage(
QRect(x + xto, y + yfrom, wto, fheight),
background,
QRect(
QPoint(xfrom, bfrom) * dpr,
QSize((wfrom ? wfrom : wto), fheight) * dpr));
};
if (width < bwidth) {
const auto xhalf = width / 2;
fill(0, xhalf, 0);
fill(xhalf, width - xhalf, bwidth - (width - xhalf));
} else if (width == bwidth) {
fill(0, width, 0);
} else {
const auto half = bwidth / (2 * dpr);
fill(0, half, 0);
fill(width - half, half, bwidth - half);
fill(half, width - 2 * half, half, 1);
}
};
if (height < bheight) {
fillRow(0, height / 2, 0);
fillRow(height / 2, height, bheight - (height - (height / 2)));
} else {
fillRow(0, height, 0);
}
auto hq = PainterHighQualityEnabler(p);
const auto progress = _selectedAnimation.value(_selected ? 1. : 0.);
const auto pwidth = progress * st::defaultRoundCheckbox.width;
p.setPen(QPen(st::defaultRoundCheckbox.bgActive->c, pwidth));
p.setBrush(Qt::NoBrush);
const auto rounded = rect().marginsRemoved(_extend);
const auto phalf = pwidth / 2.;
const auto extended = QRectF(rounded).marginsRemoved(
{ phalf, phalf, phalf, phalf });
const auto xradius = removed.left() + st::giftBoxGiftRadius - phalf;
const auto yradius = removed.top() + st::giftBoxGiftRadius - phalf;
p.drawRoundedRect(extended, xradius, yradius);
}
void GiftButton::resizeEvent(QResizeEvent *e) {
if (!_button.isEmpty()) {
_button.moveLeft((width() - _button.width()) / 2);
@ -267,9 +342,10 @@ void GiftButton::cacheUniqueBackground(
[[maybe_unused]] const auto preload = _uniquePatternEmoji->ready();
}
const auto outer = QRect(0, 0, width, height);
const auto extend = currentExtend();
const auto inner = outer.marginsRemoved(
_extend
).translated(-_extend.left(), -_extend.top());
extend
).translated(-extend.left(), -extend.top());
const auto ratio = style::DevicePixelRatio();
if (_uniqueBackgroundCache.size() != inner.size() * ratio) {
_uniqueBackgroundCache = QImage(
@ -313,32 +389,15 @@ void GiftButton::paintEvent(QPaintEvent *e) {
: nullptr;
const auto hidden = v::is<GiftTypeStars>(_descriptor)
&& v::get<GiftTypeStars>(_descriptor).hidden;;
const auto position = QPoint(_extend.left(), _extend.top());
const auto extend = currentExtend();
const auto position = QPoint(extend.left(), extend.top());
const auto background = _delegate->background();
const auto dpr = int(background.devicePixelRatio());
const auto width = this->width();
if (width * dpr <= background.width()) {
p.drawImage(0, 0, background);
} else {
const auto full = background.width();
const auto half = ((full / 2) / dpr) * dpr;
const auto height = background.height();
p.drawImage(
QRect(0, 0, half / dpr, height / dpr),
background,
QRect(0, 0, half, height));
p.drawImage(
QRect(width - (half / dpr), 0, half / dpr, height / dpr),
background,
QRect(full - half, 0, half, height));
p.drawImage(
QRect(half / dpr, 0, width - 2 * (half / dpr), height / dpr),
background,
QRect(half, 0, 1, height));
}
const auto dpr = int(background.devicePixelRatio());
paintBackground(p, background);
if (unique) {
cacheUniqueBackground(unique, width, background.height() / dpr);
p.drawImage(_extend.left(), _extend.top(), _uniqueBackgroundCache);
p.drawImage(extend.left(), extend.top(), _uniqueBackgroundCache);
}
if (_userpic) {
@ -348,7 +407,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
}
const auto image = _userpic->image(st::giftBoxUserpicSize);
const auto skip = st::giftBoxUserpicSkip;
p.drawImage(_extend.left() + skip, _extend.top() + skip, image);
p.drawImage(extend.left() + skip, extend.top() + skip, image);
}
auto frame = QImage();
@ -401,7 +460,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
auto hq = PainterHighQualityEnabler(p);
const auto premium = v::is<GiftTypePremium>(_descriptor);
const auto singlew = width - _extend.left() - _extend.right();
const auto singlew = width - extend.left() - extend.right();
const auto font = st::semiboldFont;
p.setFont(font);
@ -420,11 +479,17 @@ void GiftButton::paintEvent(QPaintEvent *e) {
}
return GiftBadge();
}, [&](const GiftTypeStars &data) {
if (const auto count = data.info.limitedCount) {
const auto soldOut = !data.userpic && !data.info.limitedLeft;
const auto count = data.info.limitedCount;
const auto pinned = data.pinned || data.pinnedSelection;
if (count || pinned) {
const auto soldOut = !pinned
&& !data.userpic
&& !data.info.limitedLeft;
return GiftBadge{
.text = (soldOut
? tr::lng_gift_stars_sold_out(tr::now)
: (unique && pinned)
? ('#' + QString::number(unique->number))
: (!data.userpic && !data.info.unique)
? tr::lng_gift_stars_limited(tr::now)
: (count == 1)
@ -452,7 +517,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
if (badge) {
const auto rubberOut = st::lineWidth;
const auto inner = rect().marginsRemoved(_extend);
const auto inner = rect().marginsRemoved(extend);
p.setClipRect(inner.marginsAdded(
{ rubberOut, rubberOut, rubberOut, rubberOut }));
@ -474,7 +539,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
p.setPen(Qt::NoPen);
p.setBrush(unique->backdrop.patternColor);
const auto rect = QRect(
QPoint(_extend.left() + skip, _extend.top() + skip),
QPoint(extend.left() + skip, extend.top() + skip),
QSize(icon.width() + 2 * add, icon.height() + 2 * add));
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
@ -547,12 +612,10 @@ void GiftButton::paintEvent(QPaintEvent *e) {
}
}
Delegate::Delegate(
not_null<Window::SessionController*> window,
GiftButtonMode mode)
: _window(window)
Delegate::Delegate(not_null<Main::Session*> session, GiftButtonMode mode)
: _session(session)
, _hiddenMark(std::make_unique<StickerPremiumMark>(
&window->session(),
_session,
st::giftBoxHiddenMark,
RectPart::Center))
, _mode(mode) {
@ -563,18 +626,17 @@ Delegate::Delegate(Delegate &&other) = default;
Delegate::~Delegate() = default;
TextWithEntities Delegate::star() {
const auto owner = &_window->session().data();
return owner->customEmojiManager().creditsEmoji();
return _session->data().customEmojiManager().creditsEmoji();
}
TextWithEntities Delegate::ministar() {
const auto owner = &_window->session().data();
const auto owner = &_session->data();
const auto top = st::giftBoxByStarsStarTop;
return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 });
}
Ui::Text::MarkedContext Delegate::textContext() {
return Core::TextContext({ .session = &_window->session() });
return Core::TextContext({ .session = _session });
}
QSize Delegate::buttonSize() {
@ -601,7 +663,7 @@ auto Delegate::buttonPatternEmoji(
not_null<Data::UniqueGift*> unique,
Fn<void()> repaint)
-> std::unique_ptr<Ui::Text::CustomEmoji> {
return _window->session().data().customEmojiManager().create(
return _session->data().customEmojiManager().create(
unique->pattern.document,
repaint,
Data::CustomEmojiSizeTag::Large);
@ -658,7 +720,7 @@ QImage Delegate::background() {
rpl::producer<not_null<DocumentData*>> Delegate::sticker(
const GiftDescriptor &descriptor) {
return GiftStickerValue(&_window->session(), descriptor);
return GiftStickerValue(_session, descriptor);
}
not_null<StickerPremiumMark*> Delegate::hiddenMark() {
@ -784,4 +846,143 @@ QImage ValidateRotatedBadge(const GiftBadge &badge, int added) {
return result;
}
void SelectGiftToUnpin(
std::shared_ptr<ChatHelpers::Show> show,
const std::vector<Data::CreditsHistoryEntry> &pinned,
Fn<void(Data::SavedStarGiftId)> chosen) {
show->show(Box([=](not_null<Ui::GenericBox*> box) {
struct State {
explicit State(not_null<Main::Session*> session)
: delegate(session, GiftButtonMode::Minimal) {
}
Delegate delegate;
rpl::variable<int> selected = -1;
std::vector<not_null<GiftButton*>> buttons;
};
const auto session = &show->session();
const auto state = box->lifetime().make_state<State>(session);
box->setStyle(st::giftTooManyPinnedBox);
box->setWidth(st::boxWideWidth);
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_gift_many_pinned_title(),
st::giftBoxSubtitle),
st::giftBoxSubtitleMargin);
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_gift_many_pinned_choose(),
st::giftTooManyPinnedChoose),
st::giftBoxAboutMargin);
const auto gifts = box->addRow(
object_ptr<Ui::RpWidget>(box),
QMargins(
st::giftBoxPadding.left(),
st::giftTooManyPinnedBox.buttonPadding.top(),
st::giftBoxPadding.right(),
0));
for (const auto &entry : pinned) {
const auto index = int(state->buttons.size());
state->buttons.push_back(
Ui::CreateChild<GiftButton>(gifts, &state->delegate));
const auto button = state->buttons.back();
button->setDescriptor(GiftTypeStars{
.info = {
.id = entry.stargiftId,
.unique = entry.uniqueGift,
.document = entry.uniqueGift->model.document,
},
.pinnedSelection = true,
}, GiftButton::Mode::Minimal);
button->setClickedCallback([=] {
const auto now = state->selected.current();
state->selected = (now == index) ? -1 : index;
});
}
state->selected.value(
) | rpl::combine_previous(
) | rpl::start_with_next([=](int old, int now) {
if (old >= 0) state->buttons[old]->toggleSelected(false);
if (now >= 0) state->buttons[now]->toggleSelected(true);
}, gifts->lifetime());
gifts->widthValue() | rpl::start_with_next([=](int width) {
const auto singleMin = state->delegate.buttonSize();
if (width < singleMin.width()) {
return;
}
const auto count = int(state->buttons.size());
const auto skipw = st::giftBoxGiftSkip.x();
const auto skiph = st::giftBoxGiftSkip.y();
const auto perRow = std::min(
(width + skipw) / (singleMin.width() + skipw),
std::max(count, 1));
if (perRow <= 0) {
return;
}
const auto single = (width - (perRow - 1) * skipw) / perRow;
const auto height = singleMin.height();
const auto rows = (count + perRow - 1) / perRow;
for (auto row = 0; row != rows; ++row) {
const auto y = row * (height + skiph);
for (auto column = 0; column != perRow; ++column) {
const auto index = row * perRow + column;
if (index >= count) {
break;
}
const auto &button = state->buttons[index];
const auto x = column * (single + skipw);
button->setGeometry(
QRect(x, y, single, height),
state->delegate.buttonExtend());
}
}
gifts->resize(width, rows * (height + skiph) - skiph);
}, gifts->lifetime());
const auto button = box->addButton(rpl::single(QString()), [=] {
const auto index = state->selected.current();
if (index < 0) {
return;
}
Assert(index < int(pinned.size()));
const auto &entry = pinned[index];
const auto weak = Ui::MakeWeak(box);
chosen(::Settings::EntryToSavedStarGiftId(session, entry));
if (const auto strong = weak.data()) {
strong->closeBox();
}
});
const auto label = Ui::SetButtonMarkedLabel(
button,
tr::lng_context_unpin_from_top(Ui::Text::WithEntities),
&show->session(),
st::creditsBoxButtonLabel,
&st::giftTooManyPinnedBox.button.textFg);
state->selected.value() | rpl::start_with_next([=](int value) {
const auto has = (value >= 0);
label->setOpacity(has ? 1. : 0.5);
button->setAttribute(Qt::WA_TransparentForMouseEvents, !has);
}, box->lifetime());
const auto buttonPadding = st::giftTooManyPinnedBox.buttonPadding;
const auto buttonWidth = st::boxWideWidth
- buttonPadding.left()
- buttonPadding.right();
button->resizeToWidth(buttonWidth);
button->widthValue() | rpl::start_with_next([=](int width) {
if (width != buttonWidth) {
button->resizeToWidth(buttonWidth);
}
}, button->lifetime());
}));
}
} // namespace Info::PeerGifts

View file

@ -15,8 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class StickerPremiumMark;
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
struct UniqueGift;
struct CreditsHistoryEntry;
class SavedStarGiftId;
} // namespace Data
namespace HistoryView {
@ -57,10 +63,11 @@ struct GiftTypeStars {
Data::StarGift info;
PeerData *from = nullptr;
TimeId date = 0;
bool userpic = false;
bool pinned = false;
bool hidden = false;
bool mine = false;
bool pinnedSelection : 1 = false;
bool userpic : 1 = false;
bool pinned : 1 = false;
bool hidden : 1 = false;
bool mine : 1 = false;
[[nodiscard]] friend inline bool operator==(
const GiftTypeStars&,
@ -128,6 +135,8 @@ public:
void setDescriptor(const GiftDescriptor &descriptor, Mode mode);
void setGeometry(QRect inner, QMargins extend);
void toggleSelected(bool selected);
[[nodiscard]] rpl::producer<QPoint> contextMenuRequests() const {
return _contextMenuRequests.events();
}
@ -137,6 +146,7 @@ private:
void resizeEvent(QResizeEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void paintBackground(QPainter &p, const QImage &background);
void cacheUniqueBackground(
not_null<Data::UniqueGift*> unique,
int width,
@ -144,6 +154,7 @@ private:
void setDocument(not_null<DocumentData*> document);
[[nodiscard]] bool documentResolved() const;
[[nodiscard]] QMargins currentExtend() const;
void unsubscribe();
@ -159,8 +170,10 @@ private:
std::unique_ptr<Ui::Text::CustomEmoji> _uniquePatternEmoji;
base::flat_map<float64, QImage> _uniquePatternCache;
std::optional<Ui::Premium::ColoredMiniStars> _stars;
Ui::Animations::Simple _selectedAnimation;
bool _subscribed = false;
bool _patterned = false;
bool _selected = false;
bool _small = false;
QRect _button;
@ -173,9 +186,7 @@ private:
class Delegate final : public GiftButtonDelegate {
public:
Delegate(
not_null<Window::SessionController*> window,
GiftButtonMode mode);
Delegate(not_null<Main::Session*> session, GiftButtonMode mode);
Delegate(Delegate &&other);
~Delegate();
@ -195,7 +206,7 @@ public:
QImage cachedBadge(const GiftBadge &badge) override;
private:
const not_null<Window::SessionController*> _window;
const not_null<Main::Session*> _session;
std::unique_ptr<StickerPremiumMark> _hiddenMark;
base::flat_map<GiftBadge, QImage> _badges;
QSize _single;
@ -214,4 +225,9 @@ private:
[[nodiscard]] QImage ValidateRotatedBadge(const GiftBadge &badge, int added);
void SelectGiftToUnpin(
std::shared_ptr<ChatHelpers::Show> show,
const std::vector<Data::CreditsHistoryEntry> &pinned,
Fn<void(Data::SavedStarGiftId)> chosen);
} // namespace Info::PeerGifts

View file

@ -112,6 +112,9 @@ private:
int resizeGetHeight(int width) override;
[[nodiscard]] auto pinnedSavedGifts()
-> Fn<std::vector<Data::CreditsHistoryEntry>()>;
const not_null<Window::SessionController*> _window;
rpl::variable<Filter> _filter;
Delegate _delegate;
@ -152,7 +155,7 @@ InnerWidget::InnerWidget(
: BoxContentDivider(parent)
, _window(controller->parentController())
, _filter(std::move(filter))
, _delegate(_window, GiftButtonMode::Minimal)
, _delegate(&_window->session(), GiftButtonMode::Minimal)
, _controller(controller)
, _peer(peer)
, _totalCount(_peer->peerGiftsCount())
@ -225,6 +228,9 @@ void InnerWidget::subscribeToUpdates() {
return;
}
refreshButtons();
if (update.action == Action::Pin) {
_scrollToTop.fire({});
}
}, lifetime());
}
@ -476,6 +482,41 @@ void InnerWidget::validateButtons() {
std::swap(_views, views);
}
auto InnerWidget::pinnedSavedGifts()
-> Fn<std::vector<Data::CreditsHistoryEntry>()> {
struct Entry {
Data::SavedStarGiftId id;
std::shared_ptr<Data::UniqueGift> unique;
};
auto entries = std::vector<Entry>();
for (const auto &entry : _entries) {
if (entry.gift.pinned) {
Assert(entry.gift.info.unique != nullptr);
entries.push_back({
entry.gift.manageId,
entry.gift.info.unique,
});
} else {
break;
}
}
return [entries] {
auto result = std::vector<Data::CreditsHistoryEntry>();
result.reserve(entries.size());
for (const auto &entry : entries) {
const auto &id = entry.id;
result.push_back({
.bareMsgId = uint64(id.userMessageId().bare),
.bareEntryOwnerId = id.chat() ? id.chat()->id.value : 0,
.giftChannelSavedId = id.chatSavedId(),
.uniqueGift = entry.unique,
.stargift = true,
});
}
return result;
};
}
void InnerWidget::showMenuFor(not_null<GiftButton*> button, QPoint point) {
if (_menu) {
return;
@ -495,27 +536,7 @@ void InnerWidget::showMenuFor(not_null<GiftButton*> button, QPoint point) {
auto entry = ::Settings::SavedStarGiftEntry(
_peer,
_entries[index].gift);
auto pinnedIds = std::vector<Data::SavedStarGiftId>();
for (const auto &entry : _entries) {
if (entry.gift.pinned) {
pinnedIds.push_back(entry.gift.manageId);
} else {
break;
}
}
entry.pinnedSavedGifts = [pinnedIds] {
auto result = std::vector<Data::CreditsHistoryEntry>();
result.reserve(pinnedIds.size());
for (const auto &id : pinnedIds) {
result.push_back({
.bareMsgId = uint64(id.userMessageId().bare),
.bareEntryOwnerId = id.chat() ? id.chat()->id.value : 0,
.giftChannelSavedId = id.chatSavedId(),
.stargift = true,
});
}
return result;
};
entry.pinnedSavedGifts = pinnedSavedGifts();
_menu = base::make_unique_q<Ui::PopupMenu>(this, st::popupMenuWithIcons);
::Settings::FillSavedStarGiftMenu(
_controller->uiShow(),
@ -535,7 +556,8 @@ void InnerWidget::showGift(int index) {
::Settings::SavedStarGiftBox,
_window,
_peer,
_entries[index].gift));
_entries[index].gift,
pinnedSavedGifts()));
}
void InnerWidget::refreshAbout() {

View file

@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/bot/starref/info_bot_starref_common.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
#include "info/channel_statistics/earn/info_channel_earn_widget.h" // Info::ChannelEarn::Make.
#include "info/peer_gifts/info_peer_gifts_common.h"
#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
#include "info/statistics/info_statistics_list_controllers.h"
#include "info/info_controller.h"
@ -173,18 +174,6 @@ private:
};
[[nodiscard]] Data::SavedStarGiftId EntryToSavedStarGiftId(
not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &entry) {
return !entry.stargift
? Data::SavedStarGiftId()
: (entry.bareEntryOwnerId && entry.giftChannelSavedId)
? Data::SavedStarGiftId::Chat(
session->data().peer(PeerId(entry.bareEntryOwnerId)),
entry.giftChannelSavedId)
: Data::SavedStarGiftId::User(MsgId(entry.bareMsgId));
}
void ToggleStarGiftSaved(
std::shared_ptr<ChatHelpers::Show> show,
Data::SavedStarGiftId savedId,
@ -226,7 +215,8 @@ void ToggleStarGiftPinned(
Data::SavedStarGiftId savedId,
std::vector<Data::SavedStarGiftId> already,
bool pinned,
Fn<void(bool)> done = nullptr) {
std::shared_ptr<Data::UniqueGift> uniqueData = nullptr,
std::shared_ptr<Data::UniqueGift> replacingData = nullptr) {
already.erase(ranges::remove(already, savedId), end(already));
if (pinned) {
already.insert(begin(already), savedId);
@ -256,16 +246,29 @@ void ToggleStarGiftPinned(
.action = (pinned ? GiftAction::Pin : GiftAction::Unpin),
});
if (const auto onstack = done) {
onstack(true);
}
if (pinned) {
show->showToast(tr::lng_gift_pinned_done(tr::now));
show->showToast({
.title = (uniqueData
? tr::lng_gift_pinned_done_title(
tr::now,
lt_gift,
Data::UniqueGiftName(*uniqueData))
: QString()),
.text = (replacingData
? tr::lng_gift_pinned_done_replaced(
tr::now,
lt_gift,
TextWithEntities{
Data::UniqueGiftName(*replacingData),
},
Ui::Text::WithEntities)
: tr::lng_gift_pinned_done(
tr::now,
Ui::Text::WithEntities)),
.duration = Ui::Toast::kDefaultDuration * 2,
});
}
}).fail([=](const MTP::Error &error) {
if (const auto onstack = done) {
onstack(false);
}
show->showToast(error.type());
}).send();
}
@ -920,25 +923,61 @@ void FillUniqueGiftMenu(
if (unique
&& canToggle
&& e.savedToProfile
&& type == SavedStarGiftMenuType::List) {
const auto already = [session, entries = e.pinnedSavedGifts] {
Expects(entries != nullptr);
auto list = entries();
&& e.pinnedSavedGifts) {
const auto pinned = e.pinnedSavedGifts;
const auto ids = [session](
const std::vector<Data::CreditsHistoryEntry> &pinned) {
auto result = std::vector<Data::SavedStarGiftId>();
result.reserve(list.size());
for (const auto &entry : list) {
result.reserve(pinned.size());
for (const auto &entry : pinned) {
result.push_back(EntryToSavedStarGiftId(session, entry));
}
return result;
};
if (e.giftPinned) {
menu->addAction(tr::lng_context_unpin_from_top(tr::now), [=] {
ToggleStarGiftPinned(show, savedId, already(), false);
ToggleStarGiftPinned(show, savedId, ids(pinned()), false);
}, st.unpin ? st.unpin : &st::menuIconUnpin);
} else {
menu->addAction(tr::lng_context_pin_to_top(tr::now), [=] {
ToggleStarGiftPinned(show, savedId, already(), true);
const auto list = pinned();
const auto &appConfig = show->session().appConfig();
const auto limit = appConfig.pinnedGiftsLimit();
auto already = ids(list);
if (list.size() >= limit) {
Info::PeerGifts::SelectGiftToUnpin(show, list, [=](
Data::SavedStarGiftId id) {
auto copy = already;
const auto i = ranges::find(copy, id);
const auto replaced = (i != end(copy))
? list[i - begin(copy)].uniqueGift
: nullptr;
if (i != end(copy)) {
copy.erase(i);
}
using GiftAction = Data::GiftUpdate::Action;
show->session().data().notifyGiftUpdate({
.id = id,
.action = GiftAction::Unpin,
});
ToggleStarGiftPinned(
show,
savedId,
already,
true,
unique,
replaced);
});
} else {
ToggleStarGiftPinned(
show,
savedId,
already,
true,
unique);
}
}, st.pin ? st.pin : &st::menuIconPin);
}
}
@ -2031,15 +2070,30 @@ Data::CreditsHistoryEntry SavedStarGiftEntry(
};
}
Data::SavedStarGiftId EntryToSavedStarGiftId(
not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &entry) {
return !entry.stargift
? Data::SavedStarGiftId()
: (entry.bareEntryOwnerId && entry.giftChannelSavedId)
? Data::SavedStarGiftId::Chat(
session->data().peer(PeerId(entry.bareEntryOwnerId)),
entry.giftChannelSavedId)
: Data::SavedStarGiftId::User(MsgId(entry.bareMsgId));
}
void SavedStarGiftBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<PeerData*> owner,
const Data::SavedStarGift &data) {
const Data::SavedStarGift &data,
Fn<std::vector<Data::CreditsHistoryEntry>()> pinned) {
auto entry = SavedStarGiftEntry(owner, data);
entry.pinnedSavedGifts = std::move(pinned);
Settings::ReceiptCreditsBox(
box,
controller,
SavedStarGiftEntry(owner, data),
std::move(entry),
Data::SubscriptionEntry());
}

View file

@ -24,6 +24,7 @@ struct SubscriptionEntry;
struct GiftCode;
struct CreditTopupOption;
struct SavedStarGift;
class SavedStarGiftId;
struct StarGift;
} // namespace Data
@ -158,11 +159,15 @@ void GlobalStarGiftBox(
[[nodiscard]] Data::CreditsHistoryEntry SavedStarGiftEntry(
not_null<PeerData*> owner,
const Data::SavedStarGift &data);
[[nodiscard]] Data::SavedStarGiftId EntryToSavedStarGiftId(
not_null<Main::Session*> session,
const Data::CreditsHistoryEntry &entry);
void SavedStarGiftBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<PeerData*> owner,
const Data::SavedStarGift &data);
const Data::SavedStarGift &data,
Fn<std::vector<Data::CreditsHistoryEntry>()> pinned = nullptr);
enum class SavedStarGiftMenuType {
List,
View,

View file

@ -149,6 +149,7 @@ giftBoxStickerStarTop: 24px;
giftBoxSmallStickerTop: 16px;
giftBoxStickerTopByStars: -4px;
giftBoxStickerSize: size(80px, 80px);
giftBoxSelectSkip: 5px;
giftBoxUserpicSize: 24px;
giftBoxUserpicSkip: 2px;
giftBoxTextField: InputField(defaultInputField) {
@ -256,3 +257,10 @@ darkUpgradeGiftInfoTitle: FlatLabel(defaultFlatLabel) {
darkUpgradeGiftInfoAbout: FlatLabel(upgradeGiftSubtext) {
textFg: groupCallMemberNotJoinedStatus;
}
giftTooManyPinnedBox: Box(giftBox) {
buttonPadding: margins(11px, 11px, 11px, 11px);
}
giftTooManyPinnedChoose: FlatLabel(giftBoxAbout) {
textFg: windowSubTextFg;
}