Load my gifts while scrolling in Send Gift.

This commit is contained in:
John Preston 2025-03-21 14:52:57 +04:00
parent e1f4fd0c7b
commit 696be858f6

View file

@ -369,6 +369,30 @@ auto GenerateGiftMedia(
return Images::Round(std::move(result), mask, RectPart::FullTop); return Images::Round(std::move(result), mask, RectPart::FullTop);
} }
struct VisibleRange {
int top = 0;
int bottom = 0;
friend inline bool operator==(VisibleRange, VisibleRange) = default;
};
class WidgetWithRange final : public RpWidget {
public:
using RpWidget::RpWidget;
[[nodiscard]] rpl::producer<VisibleRange> visibleRange() const {
return _visibleRange.value();
}
private:
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override {
_visibleRange = VisibleRange{ visibleTop, visibleBottom };
}
rpl::variable<VisibleRange> _visibleRange;
};
void PrepareImage( void PrepareImage(
QImage &image, QImage &image,
not_null<Text::CustomEmoji*> emoji, not_null<Text::CustomEmoji*> emoji,
@ -767,7 +791,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
} }
[[nodiscard]] rpl::producer<MyGiftsDescriptor> UniqueGiftsSlice( [[nodiscard]] rpl::producer<MyGiftsDescriptor> UniqueGiftsSlice(
not_null<Main::Session*> session) { not_null<Main::Session*> session,
QString offset = QString()) {
return [=](auto consumer) { return [=](auto consumer) {
using Flag = MTPpayments_GetSavedStarGifts::Flag; using Flag = MTPpayments_GetSavedStarGifts::Flag;
const auto user = session->user(); const auto user = session->user();
@ -775,7 +800,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
MTPpayments_GetSavedStarGifts( MTPpayments_GetSavedStarGifts(
MTP_flags(Flag::f_exclude_limited | Flag::f_exclude_unlimited), MTP_flags(Flag::f_exclude_limited | Flag::f_exclude_unlimited),
user->input, user->input,
MTP_string(QString()), MTP_string(offset),
MTP_int(kMyGiftsPerPage) MTP_int(kMyGiftsPerPage)
)).done([=](const MTPpayments_SavedStarGifts &result) { )).done([=](const MTPpayments_SavedStarGifts &result) {
auto gifts = MyGiftsDescriptor(); auto gifts = MyGiftsDescriptor();
@ -1798,14 +1823,21 @@ void SendGiftBox(
[[nodiscard]] object_ptr<RpWidget> MakeGiftsList( [[nodiscard]] object_ptr<RpWidget> MakeGiftsList(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer, not_null<PeerData*> peer,
rpl::producer<GiftsDescriptor> gifts) { rpl::producer<GiftsDescriptor> gifts,
auto result = object_ptr<RpWidget>((QWidget*)nullptr); Fn<void()> loadMore) {
auto result = object_ptr<WidgetWithRange>((QWidget*)nullptr);
const auto raw = result.data(); const auto raw = result.data();
struct State { struct State {
Delegate delegate; Delegate delegate;
std::vector<int> order;
std::vector<bool> validated;
std::vector<GiftDescriptor> list;
std::vector<std::unique_ptr<GiftButton>> buttons; std::vector<std::unique_ptr<GiftButton>> buttons;
std::shared_ptr<Api::PremiumGiftCodeOptions> api;
rpl::variable<VisibleRange> visibleRange;
bool sending = false; bool sending = false;
int perRow = 1;
}; };
const auto state = raw->lifetime().make_state<State>(State{ const auto state = raw->lifetime().make_state<State>(State{
.delegate = Delegate(&window->session(), GiftButtonMode::Full), .delegate = Delegate(&window->session(), GiftButtonMode::Full),
@ -1821,54 +1853,65 @@ void SendGiftBox(
} }
}, raw->lifetime()); }, raw->lifetime());
std::move( const auto rebuild = [=] {
gifts
) | rpl::start_with_next([=](const GiftsDescriptor &gifts) {
const auto width = st::boxWideWidth; const auto width = st::boxWideWidth;
const auto padding = st::giftBoxPadding; const auto padding = st::giftBoxPadding;
const auto available = width - padding.left() - padding.right(); const auto available = width - padding.left() - padding.right();
const auto perRow = available / single.width(); const auto range = state->visibleRange.current();
const auto count = int(gifts.list.size()); const auto count = int(state->list.size());
auto order = ranges::views::ints auto &buttons = state->buttons;
| ranges::views::take(count) if (buttons.size() < count) {
| ranges::to_vector; buttons.resize(count);
if (SortForBirthday(peer)) {
ranges::stable_partition(order, [&](int i) {
const auto &gift = gifts.list[i];
const auto stars = std::get_if<GiftTypeStars>(&gift);
return stars && stars->info.birthday;
});
} }
auto &validated = state->validated;
validated.resize(count);
auto x = padding.left(); auto x = padding.left();
auto y = padding.top(); auto y = padding.top();
state->buttons.resize(count); const auto perRow = state->perRow;
for (auto &button : state->buttons) { const auto singlew = single.width() + st::giftBoxGiftSkip.x();
const auto singleh = single.height() + st::giftBoxGiftSkip.y();
const auto rowFrom = std::max(range.top - y, 0) / singleh;
const auto rowTill = (std::max(range.bottom - y + st::giftBoxGiftSkip.y(), 0) + singleh - 1)
/ singleh;
Assert(rowTill >= rowFrom);
const auto first = rowFrom * perRow;
const auto last = std::min(rowTill * perRow, count);
auto checkedFrom = 0;
auto checkedTill = int(buttons.size());
const auto ensureButton = [&](int index) {
auto &button = buttons[index];
if (!button) { if (!button) {
button = std::make_unique<GiftButton>(raw, &state->delegate); validated[index] = false;
button->show(); for (; checkedFrom != first; ++checkedFrom) {
if (buttons[checkedFrom]) {
button = std::move(buttons[checkedFrom]);
break;
}
}
} }
} if (!button) {
const auto api = gifts.api; for (; checkedTill != last; ) {
for (auto i = 0; i != count; ++i) { --checkedTill;
const auto button = state->buttons[i].get(); if (buttons[checkedTill]) {
const auto &descriptor = gifts.list[order[i]]; button = std::move(buttons[checkedTill]);
break;
}
}
}
if (!button) {
button = std::make_unique<GiftButton>(
raw,
&state->delegate);
}
if (validated[index]) {
return;
}
button->show();
validated[index] = true;
const auto &descriptor = state->list[state->order[index]];
button->setDescriptor(descriptor, GiftButton::Mode::Full); button->setDescriptor(descriptor, GiftButton::Mode::Full);
const auto last = !((i + 1) % perRow);
if (last) {
x = padding.left() + available - single.width();
}
button->setGeometry(QRect(QPoint(x, y), single), extend);
if (last) {
x = padding.left();
y += single.height() + st::giftBoxGiftSkip.y();
} else {
x += single.width() + st::giftBoxGiftSkip.x();
}
button->setClickedCallback([=] { button->setClickedCallback([=] {
const auto star = std::get_if<GiftTypeStars>(&descriptor); const auto star = std::get_if<GiftTypeStars>(&descriptor);
if (star && star->info.unique) { if (star && star->info.unique) {
@ -1885,17 +1928,84 @@ void SendGiftBox(
} else if (star && IsSoldOut(star->info)) { } else if (star && IsSoldOut(star->info)) {
window->show(Box(SoldOutBox, window, *star)); window->show(Box(SoldOutBox, window, *star));
} else { } else {
window->show( window->show(Box(
Box(SendGiftBox, window, peer, api, descriptor)); SendGiftBox,
window,
peer,
state->api,
descriptor));
} }
}); });
button->setGeometry(QRect(QPoint(x, y), single), extend);
};
y += rowFrom * singleh;
for (auto row = rowFrom; row != rowTill; ++row) {
for (auto col = 0; col != perRow; ++col) {
const auto index = row * perRow + col;
if (index >= count) {
break;
}
const auto last = !((col + 1) % perRow);
if (last) {
x = padding.left() + available - single.width();
}
ensureButton(index);
if (last) {
x = padding.left();
y += singleh;
} else {
x += singlew;
}
}
} }
if (count % perRow) { const auto till = std::min(int(buttons.size()), rowTill * perRow);
y += padding.bottom() + single.height(); for (auto i = count; i < till; ++i) {
} else { if (const auto button = buttons[i].get()) {
y += padding.bottom() - st::giftBoxGiftSkip.y(); button->hide();
}
} }
raw->resize(raw->width(), count ? y : 0);
const auto page = range.bottom - range.top;
if (loadMore && page > 0 && range.bottom + page > raw->height()) {
loadMore();
}
};
state->visibleRange = raw->visibleRange();
state->visibleRange.value(
) | rpl::start_with_next(rebuild, raw->lifetime());
std::move(
gifts
) | rpl::start_with_next([=](const GiftsDescriptor &gifts) {
const auto width = st::boxWideWidth;
const auto padding = st::giftBoxPadding;
const auto available = width - padding.left() - padding.right();
state->perRow = available / single.width();
state->list = std::move(gifts.list);
state->api = gifts.api;
const auto count = int(state->list.size());
state->order = ranges::views::ints
| ranges::views::take(count)
| ranges::to_vector;
state->validated.clear();
if (SortForBirthday(peer)) {
ranges::stable_partition(state->order, [&](int i) {
const auto &gift = state->list[i];
const auto stars = std::get_if<GiftTypeStars>(&gift);
return stars && stars->info.birthday && !stars->info.unique;
});
}
const auto rows = (count + state->perRow - 1) / state->perRow;
const auto height = padding.top()
+ (rows * single.height())
+ ((rows - 1) * st::giftBoxGiftSkip.y())
+ padding.bottom();
raw->resize(raw->width(), height);
rebuild();
}, raw->lifetime()); }, raw->lifetime());
return result; return result;
@ -1959,7 +2069,7 @@ void AddBlock(
gifts.list | ranges::to<std::vector<GiftDescriptor>>, gifts.list | ranges::to<std::vector<GiftDescriptor>>,
gifts.api, gifts.api,
}; };
})); }), nullptr);
result->lifetime().add([state = std::move(state)] {}); result->lifetime().add([state = std::move(state)] {});
return result; return result;
} }
@ -1973,8 +2083,12 @@ void AddBlock(
struct State { struct State {
rpl::variable<std::vector<GiftTypeStars>> gifts; rpl::variable<std::vector<GiftTypeStars>> gifts;
rpl::variable<int> priceTab = kPriceTabAll; rpl::variable<int> priceTab = kPriceTabAll;
rpl::event_stream<> myUpdated;
MyGiftsDescriptor my;
rpl::lifetime myLoading;
}; };
const auto state = result->lifetime().make_state<State>(); const auto state = result->lifetime().make_state<State>();
state->my = std::move(my);
state->gifts = GiftsStars(&window->session(), peer); state->gifts = GiftsStars(&window->session(), peer);
@ -1982,16 +2096,17 @@ void AddBlock(
window, window,
peer, peer,
state->gifts.value(), state->gifts.value(),
!my.list.empty()); !state->my.list.empty());
state->priceTab = std::move(tabs.priceTab); state->priceTab = std::move(tabs.priceTab);
result->add(std::move(tabs.widget)); result->add(std::move(tabs.widget));
result->add(MakeGiftsList(window, peer, rpl::combine( result->add(MakeGiftsList(window, peer, rpl::combine(
state->gifts.value(), state->gifts.value(),
state->priceTab.value() state->priceTab.value(),
) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) { rpl::single(rpl::empty) | rpl::then(state->myUpdated.events())
) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price, auto) {
if (price == kPriceTabMy) { if (price == kPriceTabMy) {
gifts.clear(); gifts.clear();
for (const auto &gift : my.list) { for (const auto &gift : state->my.list) {
gifts.push_back({ gifts.push_back({
.transferId = gift.manageId, .transferId = gift.manageId,
.info = gift.info, .info = gift.info,
@ -2011,7 +2126,26 @@ void AddBlock(
return GiftsDescriptor{ return GiftsDescriptor{
gifts | ranges::to<std::vector<GiftDescriptor>>(), gifts | ranges::to<std::vector<GiftDescriptor>>(),
}; };
}))); }), [=] {
if (state->priceTab.current() == kPriceTabMy
&& !state->my.offset.isEmpty()
&& !state->myLoading) {
state->myLoading = UniqueGiftsSlice(
&peer->session(),
state->my.offset
) | rpl::start_with_next([=](MyGiftsDescriptor &&descriptor) {
state->myLoading.destroy();
state->my.offset = descriptor.list.empty()
? QString()
: descriptor.offset;
state->my.list.insert(
end(state->my.list),
std::make_move_iterator(begin(descriptor.list)),
std::make_move_iterator(end(descriptor.list)));
state->myUpdated.fire({});
});
}
}));
return result; return result;
} }