From 696be858f64275a258865b0faf6158ec6babbb7b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Mar 2025 14:52:57 +0400 Subject: [PATCH] Load my gifts while scrolling in Send Gift. --- Telegram/SourceFiles/boxes/star_gift_box.cpp | 242 ++++++++++++++----- 1 file changed, 188 insertions(+), 54 deletions(-) diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index a149fc3979..83e4d27d83 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -369,6 +369,30 @@ auto GenerateGiftMedia( 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() const { + return _visibleRange.value(); + } +private: + void visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) override { + _visibleRange = VisibleRange{ visibleTop, visibleBottom }; + } + + rpl::variable _visibleRange; + +}; + void PrepareImage( QImage &image, not_null emoji, @@ -767,7 +791,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { } [[nodiscard]] rpl::producer UniqueGiftsSlice( - not_null session) { + not_null session, + QString offset = QString()) { return [=](auto consumer) { using Flag = MTPpayments_GetSavedStarGifts::Flag; const auto user = session->user(); @@ -775,7 +800,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { MTPpayments_GetSavedStarGifts( MTP_flags(Flag::f_exclude_limited | Flag::f_exclude_unlimited), user->input, - MTP_string(QString()), + MTP_string(offset), MTP_int(kMyGiftsPerPage) )).done([=](const MTPpayments_SavedStarGifts &result) { auto gifts = MyGiftsDescriptor(); @@ -1798,14 +1823,21 @@ void SendGiftBox( [[nodiscard]] object_ptr MakeGiftsList( not_null window, not_null peer, - rpl::producer gifts) { - auto result = object_ptr((QWidget*)nullptr); + rpl::producer gifts, + Fn loadMore) { + auto result = object_ptr((QWidget*)nullptr); const auto raw = result.data(); struct State { Delegate delegate; + std::vector order; + std::vector validated; + std::vector list; std::vector> buttons; + std::shared_ptr api; + rpl::variable visibleRange; bool sending = false; + int perRow = 1; }; const auto state = raw->lifetime().make_state(State{ .delegate = Delegate(&window->session(), GiftButtonMode::Full), @@ -1821,54 +1853,65 @@ void SendGiftBox( } }, raw->lifetime()); - std::move( - gifts - ) | rpl::start_with_next([=](const GiftsDescriptor &gifts) { + const auto rebuild = [=] { const auto width = st::boxWideWidth; const auto padding = st::giftBoxPadding; const auto available = width - padding.left() - padding.right(); - const auto perRow = available / single.width(); - const auto count = int(gifts.list.size()); + const auto range = state->visibleRange.current(); + const auto count = int(state->list.size()); - auto order = ranges::views::ints - | ranges::views::take(count) - | ranges::to_vector; - - if (SortForBirthday(peer)) { - ranges::stable_partition(order, [&](int i) { - const auto &gift = gifts.list[i]; - const auto stars = std::get_if(&gift); - return stars && stars->info.birthday; - }); + auto &buttons = state->buttons; + if (buttons.size() < count) { + buttons.resize(count); } + auto &validated = state->validated; + validated.resize(count); auto x = padding.left(); auto y = padding.top(); - state->buttons.resize(count); - for (auto &button : state->buttons) { + const auto perRow = state->perRow; + 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) { - button = std::make_unique(raw, &state->delegate); - button->show(); + validated[index] = false; + for (; checkedFrom != first; ++checkedFrom) { + if (buttons[checkedFrom]) { + button = std::move(buttons[checkedFrom]); + break; + } + } } - } - const auto api = gifts.api; - for (auto i = 0; i != count; ++i) { - const auto button = state->buttons[i].get(); - const auto &descriptor = gifts.list[order[i]]; + if (!button) { + for (; checkedTill != last; ) { + --checkedTill; + if (buttons[checkedTill]) { + button = std::move(buttons[checkedTill]); + break; + } + } + } + if (!button) { + button = std::make_unique( + 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); - - 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([=] { const auto star = std::get_if(&descriptor); if (star && star->info.unique) { @@ -1885,17 +1928,84 @@ void SendGiftBox( } else if (star && IsSoldOut(star->info)) { window->show(Box(SoldOutBox, window, *star)); } else { - window->show( - Box(SendGiftBox, window, peer, api, descriptor)); + window->show(Box( + 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) { - y += padding.bottom() + single.height(); - } else { - y += padding.bottom() - st::giftBoxGiftSkip.y(); + const auto till = std::min(int(buttons.size()), rowTill * perRow); + for (auto i = count; i < till; ++i) { + if (const auto button = buttons[i].get()) { + 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(&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()); return result; @@ -1959,7 +2069,7 @@ void AddBlock( gifts.list | ranges::to>, gifts.api, }; - })); + }), nullptr); result->lifetime().add([state = std::move(state)] {}); return result; } @@ -1973,8 +2083,12 @@ void AddBlock( struct State { rpl::variable> gifts; rpl::variable priceTab = kPriceTabAll; + rpl::event_stream<> myUpdated; + MyGiftsDescriptor my; + rpl::lifetime myLoading; }; const auto state = result->lifetime().make_state(); + state->my = std::move(my); state->gifts = GiftsStars(&window->session(), peer); @@ -1982,16 +2096,17 @@ void AddBlock( window, peer, state->gifts.value(), - !my.list.empty()); + !state->my.list.empty()); state->priceTab = std::move(tabs.priceTab); result->add(std::move(tabs.widget)); result->add(MakeGiftsList(window, peer, rpl::combine( state->gifts.value(), - state->priceTab.value() - ) | rpl::map([=](std::vector &&gifts, int price) { + state->priceTab.value(), + rpl::single(rpl::empty) | rpl::then(state->myUpdated.events()) + ) | rpl::map([=](std::vector &&gifts, int price, auto) { if (price == kPriceTabMy) { gifts.clear(); - for (const auto &gift : my.list) { + for (const auto &gift : state->my.list) { gifts.push_back({ .transferId = gift.manageId, .info = gift.info, @@ -2011,7 +2126,26 @@ void AddBlock( return GiftsDescriptor{ gifts | ranges::to>(), }; - }))); + }), [=] { + 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; }