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_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" = "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_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" = "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#one" = "You got **{count} Star** for this gift.";
"lng_gift_got_stars#other" = "You got **{count} Stars** 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."; "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_subscribe" = "Subscribe to {link} to wear collectibles.";
"lng_gift_wear_start_toast" = "You put on {name}"; "lng_gift_wear_start_toast" = "You put on {name}";
"lng_gift_wear_end_toast" = "You took off {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_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; "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(); unique->exportAt = data.vcan_export_at().value_or_empty();
} }
using Id = Data::SavedStarGiftId; using Id = Data::SavedStarGiftId;
const auto hasUnique = parsed->unique != nullptr;
return Data::SavedStarGift{ return Data::SavedStarGift{
.info = std::move(*parsed), .info = std::move(*parsed),
.manageId = (to->isUser() .manageId = (to->isUser()
@ -905,7 +906,7 @@ std::optional<Data::SavedStarGift> FromTL(
.date = data.vdate().v, .date = data.vdate().v,
.upgradable = data.is_can_upgrade(), .upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(), .anonymous = data.is_name_hidden(),
.pinned = data.is_pinned_to_top(), .pinned = data.is_pinned_to_top() && hasUnique,
.hidden = data.is_unsaved(), .hidden = data.is_unsaved(),
.mine = to->isSelf(), .mine = to->isSelf(),
}; };

View file

@ -1725,7 +1725,7 @@ void SendGiftBox(
bool sending = false; bool sending = false;
}; };
const auto state = raw->lifetime().make_state<State>(State{ 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 single = state->delegate.buttonSize();
const auto shadow = st::defaultDropdownMenu.wrap.shadow; const auto shadow = st::defaultDropdownMenu.wrap.shadow;

View file

@ -479,7 +479,8 @@ ClickHandlerPtr OpenStarGiftLink(not_null<HistoryItem*> item) {
Settings::SavedStarGiftBox, Settings::SavedStarGiftBox,
window, window,
owner, owner,
*parsed)); *parsed,
nullptr));
} }
} }
}).fail([=](const MTP::Error &error) { }).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 "info/peer_gifts/info_peer_gifts_common.h"
#include "boxes/send_credits_box.h" // SetButtonMarkedLabel
#include "boxes/star_gift_box.h" #include "boxes/star_gift_box.h"
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_gift_box_pack.h"
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/data_credits.h" // CreditsHistoryEntry
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "history/view/media/history_view_sticker_player.h" #include "history/view/media/history_view_sticker_player.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.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/format_values.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/dynamic_image.h" #include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h" #include "ui/dynamic_thumbnails.h"
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
@ -240,6 +245,76 @@ void GiftButton::setGeometry(QRect inner, QMargins extend) {
AbstractButton::setGeometry(inner.marginsAdded(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) { void GiftButton::resizeEvent(QResizeEvent *e) {
if (!_button.isEmpty()) { if (!_button.isEmpty()) {
_button.moveLeft((width() - _button.width()) / 2); _button.moveLeft((width() - _button.width()) / 2);
@ -267,9 +342,10 @@ void GiftButton::cacheUniqueBackground(
[[maybe_unused]] const auto preload = _uniquePatternEmoji->ready(); [[maybe_unused]] const auto preload = _uniquePatternEmoji->ready();
} }
const auto outer = QRect(0, 0, width, height); const auto outer = QRect(0, 0, width, height);
const auto extend = currentExtend();
const auto inner = outer.marginsRemoved( const auto inner = outer.marginsRemoved(
_extend extend
).translated(-_extend.left(), -_extend.top()); ).translated(-extend.left(), -extend.top());
const auto ratio = style::DevicePixelRatio(); const auto ratio = style::DevicePixelRatio();
if (_uniqueBackgroundCache.size() != inner.size() * ratio) { if (_uniqueBackgroundCache.size() != inner.size() * ratio) {
_uniqueBackgroundCache = QImage( _uniqueBackgroundCache = QImage(
@ -313,32 +389,15 @@ void GiftButton::paintEvent(QPaintEvent *e) {
: nullptr; : nullptr;
const auto hidden = v::is<GiftTypeStars>(_descriptor) const auto hidden = v::is<GiftTypeStars>(_descriptor)
&& v::get<GiftTypeStars>(_descriptor).hidden;; && 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 background = _delegate->background();
const auto dpr = int(background.devicePixelRatio());
const auto width = this->width(); const auto width = this->width();
if (width * dpr <= background.width()) { const auto dpr = int(background.devicePixelRatio());
p.drawImage(0, 0, background); paintBackground(p, 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));
}
if (unique) { if (unique) {
cacheUniqueBackground(unique, width, background.height() / dpr); cacheUniqueBackground(unique, width, background.height() / dpr);
p.drawImage(_extend.left(), _extend.top(), _uniqueBackgroundCache); p.drawImage(extend.left(), extend.top(), _uniqueBackgroundCache);
} }
if (_userpic) { if (_userpic) {
@ -348,7 +407,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
} }
const auto image = _userpic->image(st::giftBoxUserpicSize); const auto image = _userpic->image(st::giftBoxUserpicSize);
const auto skip = st::giftBoxUserpicSkip; 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(); auto frame = QImage();
@ -401,7 +460,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
const auto premium = v::is<GiftTypePremium>(_descriptor); 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; const auto font = st::semiboldFont;
p.setFont(font); p.setFont(font);
@ -420,11 +479,17 @@ void GiftButton::paintEvent(QPaintEvent *e) {
} }
return GiftBadge(); return GiftBadge();
}, [&](const GiftTypeStars &data) { }, [&](const GiftTypeStars &data) {
if (const auto count = data.info.limitedCount) { const auto count = data.info.limitedCount;
const auto soldOut = !data.userpic && !data.info.limitedLeft; const auto pinned = data.pinned || data.pinnedSelection;
if (count || pinned) {
const auto soldOut = !pinned
&& !data.userpic
&& !data.info.limitedLeft;
return GiftBadge{ return GiftBadge{
.text = (soldOut .text = (soldOut
? tr::lng_gift_stars_sold_out(tr::now) ? tr::lng_gift_stars_sold_out(tr::now)
: (unique && pinned)
? ('#' + QString::number(unique->number))
: (!data.userpic && !data.info.unique) : (!data.userpic && !data.info.unique)
? tr::lng_gift_stars_limited(tr::now) ? tr::lng_gift_stars_limited(tr::now)
: (count == 1) : (count == 1)
@ -452,7 +517,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
if (badge) { if (badge) {
const auto rubberOut = st::lineWidth; const auto rubberOut = st::lineWidth;
const auto inner = rect().marginsRemoved(_extend); const auto inner = rect().marginsRemoved(extend);
p.setClipRect(inner.marginsAdded( p.setClipRect(inner.marginsAdded(
{ rubberOut, rubberOut, rubberOut, rubberOut })); { rubberOut, rubberOut, rubberOut, rubberOut }));
@ -474,7 +539,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(unique->backdrop.patternColor); p.setBrush(unique->backdrop.patternColor);
const auto rect = QRect( 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)); QSize(icon.width() + 2 * add, icon.height() + 2 * add));
p.drawEllipse(rect); p.drawEllipse(rect);
icon.paintInCenter(p, rect); icon.paintInCenter(p, rect);
@ -547,12 +612,10 @@ void GiftButton::paintEvent(QPaintEvent *e) {
} }
} }
Delegate::Delegate( Delegate::Delegate(not_null<Main::Session*> session, GiftButtonMode mode)
not_null<Window::SessionController*> window, : _session(session)
GiftButtonMode mode)
: _window(window)
, _hiddenMark(std::make_unique<StickerPremiumMark>( , _hiddenMark(std::make_unique<StickerPremiumMark>(
&window->session(), _session,
st::giftBoxHiddenMark, st::giftBoxHiddenMark,
RectPart::Center)) RectPart::Center))
, _mode(mode) { , _mode(mode) {
@ -563,18 +626,17 @@ Delegate::Delegate(Delegate &&other) = default;
Delegate::~Delegate() = default; Delegate::~Delegate() = default;
TextWithEntities Delegate::star() { TextWithEntities Delegate::star() {
const auto owner = &_window->session().data(); return _session->data().customEmojiManager().creditsEmoji();
return owner->customEmojiManager().creditsEmoji();
} }
TextWithEntities Delegate::ministar() { TextWithEntities Delegate::ministar() {
const auto owner = &_window->session().data(); const auto owner = &_session->data();
const auto top = st::giftBoxByStarsStarTop; const auto top = st::giftBoxByStarsStarTop;
return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 }); return owner->customEmojiManager().ministarEmoji({ 0, top, 0, 0 });
} }
Ui::Text::MarkedContext Delegate::textContext() { Ui::Text::MarkedContext Delegate::textContext() {
return Core::TextContext({ .session = &_window->session() }); return Core::TextContext({ .session = _session });
} }
QSize Delegate::buttonSize() { QSize Delegate::buttonSize() {
@ -601,7 +663,7 @@ auto Delegate::buttonPatternEmoji(
not_null<Data::UniqueGift*> unique, not_null<Data::UniqueGift*> unique,
Fn<void()> repaint) Fn<void()> repaint)
-> std::unique_ptr<Ui::Text::CustomEmoji> { -> std::unique_ptr<Ui::Text::CustomEmoji> {
return _window->session().data().customEmojiManager().create( return _session->data().customEmojiManager().create(
unique->pattern.document, unique->pattern.document,
repaint, repaint,
Data::CustomEmojiSizeTag::Large); Data::CustomEmojiSizeTag::Large);
@ -658,7 +720,7 @@ QImage Delegate::background() {
rpl::producer<not_null<DocumentData*>> Delegate::sticker( rpl::producer<not_null<DocumentData*>> Delegate::sticker(
const GiftDescriptor &descriptor) { const GiftDescriptor &descriptor) {
return GiftStickerValue(&_window->session(), descriptor); return GiftStickerValue(_session, descriptor);
} }
not_null<StickerPremiumMark*> Delegate::hiddenMark() { not_null<StickerPremiumMark*> Delegate::hiddenMark() {
@ -784,4 +846,143 @@ QImage ValidateRotatedBadge(const GiftBadge &badge, int added) {
return result; 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 } // namespace Info::PeerGifts

View file

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

View file

@ -112,6 +112,9 @@ private:
int resizeGetHeight(int width) override; int resizeGetHeight(int width) override;
[[nodiscard]] auto pinnedSavedGifts()
-> Fn<std::vector<Data::CreditsHistoryEntry>()>;
const not_null<Window::SessionController*> _window; const not_null<Window::SessionController*> _window;
rpl::variable<Filter> _filter; rpl::variable<Filter> _filter;
Delegate _delegate; Delegate _delegate;
@ -152,7 +155,7 @@ InnerWidget::InnerWidget(
: BoxContentDivider(parent) : BoxContentDivider(parent)
, _window(controller->parentController()) , _window(controller->parentController())
, _filter(std::move(filter)) , _filter(std::move(filter))
, _delegate(_window, GiftButtonMode::Minimal) , _delegate(&_window->session(), GiftButtonMode::Minimal)
, _controller(controller) , _controller(controller)
, _peer(peer) , _peer(peer)
, _totalCount(_peer->peerGiftsCount()) , _totalCount(_peer->peerGiftsCount())
@ -225,6 +228,9 @@ void InnerWidget::subscribeToUpdates() {
return; return;
} }
refreshButtons(); refreshButtons();
if (update.action == Action::Pin) {
_scrollToTop.fire({});
}
}, lifetime()); }, lifetime());
} }
@ -476,6 +482,41 @@ void InnerWidget::validateButtons() {
std::swap(_views, views); 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) { void InnerWidget::showMenuFor(not_null<GiftButton*> button, QPoint point) {
if (_menu) { if (_menu) {
return; return;
@ -495,27 +536,7 @@ void InnerWidget::showMenuFor(not_null<GiftButton*> button, QPoint point) {
auto entry = ::Settings::SavedStarGiftEntry( auto entry = ::Settings::SavedStarGiftEntry(
_peer, _peer,
_entries[index].gift); _entries[index].gift);
auto pinnedIds = std::vector<Data::SavedStarGiftId>(); entry.pinnedSavedGifts = pinnedSavedGifts();
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;
};
_menu = base::make_unique_q<Ui::PopupMenu>(this, st::popupMenuWithIcons); _menu = base::make_unique_q<Ui::PopupMenu>(this, st::popupMenuWithIcons);
::Settings::FillSavedStarGiftMenu( ::Settings::FillSavedStarGiftMenu(
_controller->uiShow(), _controller->uiShow(),
@ -535,7 +556,8 @@ void InnerWidget::showGift(int index) {
::Settings::SavedStarGiftBox, ::Settings::SavedStarGiftBox,
_window, _window,
_peer, _peer,
_entries[index].gift)); _entries[index].gift,
pinnedSavedGifts()));
} }
void InnerWidget::refreshAbout() { 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/bot/starref/info_bot_starref_common.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. #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/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/settings/info_settings_widget.h" // SectionCustomTopBarData.
#include "info/statistics/info_statistics_list_controllers.h" #include "info/statistics/info_statistics_list_controllers.h"
#include "info/info_controller.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( void ToggleStarGiftSaved(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
Data::SavedStarGiftId savedId, Data::SavedStarGiftId savedId,
@ -226,7 +215,8 @@ void ToggleStarGiftPinned(
Data::SavedStarGiftId savedId, Data::SavedStarGiftId savedId,
std::vector<Data::SavedStarGiftId> already, std::vector<Data::SavedStarGiftId> already,
bool pinned, 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)); already.erase(ranges::remove(already, savedId), end(already));
if (pinned) { if (pinned) {
already.insert(begin(already), savedId); already.insert(begin(already), savedId);
@ -256,16 +246,29 @@ void ToggleStarGiftPinned(
.action = (pinned ? GiftAction::Pin : GiftAction::Unpin), .action = (pinned ? GiftAction::Pin : GiftAction::Unpin),
}); });
if (const auto onstack = done) {
onstack(true);
}
if (pinned) { 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) { }).fail([=](const MTP::Error &error) {
if (const auto onstack = done) {
onstack(false);
}
show->showToast(error.type()); show->showToast(error.type());
}).send(); }).send();
} }
@ -920,25 +923,61 @@ void FillUniqueGiftMenu(
if (unique if (unique
&& canToggle && canToggle
&& e.savedToProfile && e.savedToProfile
&& type == SavedStarGiftMenuType::List) { && e.pinnedSavedGifts) {
const auto already = [session, entries = e.pinnedSavedGifts] { const auto pinned = e.pinnedSavedGifts;
Expects(entries != nullptr); const auto ids = [session](
const std::vector<Data::CreditsHistoryEntry> &pinned) {
auto list = entries();
auto result = std::vector<Data::SavedStarGiftId>(); auto result = std::vector<Data::SavedStarGiftId>();
result.reserve(list.size()); result.reserve(pinned.size());
for (const auto &entry : list) { for (const auto &entry : pinned) {
result.push_back(EntryToSavedStarGiftId(session, entry)); result.push_back(EntryToSavedStarGiftId(session, entry));
} }
return result; return result;
}; };
if (e.giftPinned) { if (e.giftPinned) {
menu->addAction(tr::lng_context_unpin_from_top(tr::now), [=] { 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); }, st.unpin ? st.unpin : &st::menuIconUnpin);
} else { } else {
menu->addAction(tr::lng_context_pin_to_top(tr::now), [=] { 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); }, 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( void SavedStarGiftBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> owner, 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( Settings::ReceiptCreditsBox(
box, box,
controller, controller,
SavedStarGiftEntry(owner, data), std::move(entry),
Data::SubscriptionEntry()); Data::SubscriptionEntry());
} }

View file

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

View file

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