/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/peer_gifts/info_peer_gifts_widget.h" #include "api/api_premium.h" #include "data/data_session.h" #include "data/data_user.h" #include "info/peer_gifts/info_peer_gifts_common.h" #include "info/info_controller.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/widgets/box_content_divider.h" #include "ui/widgets/labels.h" #include "ui/ui_utility.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "mtproto/sender.h" #include "window/window_session_controller.h" #include "settings/settings_credits_graphics.h" #include "styles/style_info.h" #include "styles/style_credits.h" // giftBoxPadding namespace Info::PeerGifts { namespace { constexpr auto kPreloadPages = 2; constexpr auto kPerPage = 50; [[nodiscard]] GiftDescriptor DescriptorForGift( not_null to, const Data::UserStarGift &gift) { return GiftTypeStars{ .info = gift.info, .from = ((gift.anonymous || !gift.fromId) ? nullptr : to->owner().peer(gift.fromId).get()), .userpic = !gift.info.unique, .hidden = gift.hidden, .mine = to->isSelf(), }; } } // namespace class InnerWidget final : public Ui::BoxContentDivider { public: InnerWidget( QWidget *parent, not_null controller, not_null user); [[nodiscard]] not_null user() const { return _user; } void saveState(not_null memento); void restoreState(not_null memento); private: struct Entry { Data::UserStarGift gift; GiftDescriptor descriptor; }; struct View { std::unique_ptr button; int entry = 0; }; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void subscribeToUpdates(); void loadMore(); void refreshButtons(); void validateButtons(); void showGift(int index); int resizeGetHeight(int width) override; const not_null _window; Delegate _delegate; not_null _controller; std::unique_ptr _about; const not_null _user; std::vector _entries; int _totalCount = 0; MTP::Sender _api; mtpRequestId _loadMoreRequestId = 0; QString _offset; bool _allLoaded = false; std::vector _views; int _viewsForWidth = 0; int _viewsFromRow = 0; int _viewsTillRow = 0; QSize _singleMin; QSize _single; int _perRow = 0; int _visibleFrom = 0; int _visibleTill = 0; }; InnerWidget::InnerWidget( QWidget *parent, not_null controller, not_null user) : BoxContentDivider(parent) , _window(controller->parentController()) , _delegate(_window, GiftButtonMode::Minimal) , _controller(controller) , _about(std::make_unique( this, (user->isSelf() ? tr::lng_peer_gifts_about_mine(Ui::Text::RichLangValue) : tr::lng_peer_gifts_about( lt_user, rpl::single(Ui::Text::Bold(user->shortName())), Ui::Text::RichLangValue)), st::giftListAbout)) , _user(user) , _totalCount(_user->peerGiftsCount()) , _api(&_user->session().mtp()) { _singleMin = _delegate.buttonSize(); if (user->isSelf()) { subscribeToUpdates(); } } void InnerWidget::subscribeToUpdates() { _user->owner().giftUpdates( ) | rpl::start_with_next([=](const Data::GiftUpdate &update) { const auto itemId = [](const Entry &entry) { return FullMsgId(entry.gift.fromId, entry.gift.messageId); }; const auto i = ranges::find(_entries, update.itemId, itemId); if (i == end(_entries)) { return; } const auto index = int(i - begin(_entries)); using Action = Data::GiftUpdate::Action; if (update.action == Action::Convert || update.action == Action::Delete) { _entries.erase(i); if (_totalCount > 0) { --_totalCount; } for (auto &view : _views) { if (view.entry >= index) { --view.entry; } } } else if (update.action == Action::Save || update.action == Action::Unsave) { i->gift.hidden = (update.action == Action::Unsave); v::match(i->descriptor, [](GiftTypePremium &) { }, [&](GiftTypeStars &data) { data.hidden = i->gift.hidden; }); for (auto &view : _views) { if (view.entry == index) { view.entry = -1; } } } else { return; } refreshButtons(); }, lifetime()); } void InnerWidget::visibleTopBottomUpdated( int visibleTop, int visibleBottom) { const auto page = (visibleBottom - visibleTop); if (visibleBottom + page * kPreloadPages >= height()) { loadMore(); } _visibleFrom = visibleTop; _visibleTill = visibleBottom; validateButtons(); } void InnerWidget::paintEvent(QPaintEvent *e) { auto p = QPainter(this); const auto aboutSize = _about->size().grownBy(st::giftListAboutMargin); const auto skips = QMargins(0, 0, 0, aboutSize.height()); p.fillRect(rect().marginsRemoved(skips), st::boxDividerBg->c); paintTop(p); paintBottom(p, skips.bottom()); } void InnerWidget::loadMore() { if (_allLoaded || _loadMoreRequestId) { return; } _loadMoreRequestId = _api.request(MTPpayments_GetUserStarGifts( _user->inputUser, MTP_string(_offset), MTP_int(kPerPage) )).done([=](const MTPpayments_UserStarGifts &result) { _loadMoreRequestId = 0; const auto &data = result.data(); if (const auto next = data.vnext_offset()) { _offset = qs(*next); } else { _allLoaded = true; } _totalCount = data.vcount().v; const auto owner = &_user->owner(); owner->processUsers(data.vusers()); _entries.reserve(_entries.size() + data.vgifts().v.size()); for (const auto &gift : data.vgifts().v) { if (auto parsed = Api::FromTL(_user, gift)) { auto descriptor = DescriptorForGift(_user, *parsed); _entries.push_back({ .gift = std::move(*parsed), .descriptor = std::move(descriptor), }); } } refreshButtons(); }).fail([=] { _loadMoreRequestId = 0; _allLoaded = true; }).send(); } void InnerWidget::refreshButtons() { _viewsForWidth = 0; _viewsFromRow = 0; _viewsTillRow = 0; resizeToWidth(width()); validateButtons(); } void InnerWidget::validateButtons() { if (!_perRow) { return; } const auto padding = st::giftBoxPadding; const auto vskip = padding.bottom(); const auto row = _single.height() + st::giftBoxGiftSkip.y(); const auto fromRow = std::max(_visibleFrom - vskip, 0) / row; const auto tillRow = (_visibleTill - vskip + row - 1) / row; Assert(tillRow >= fromRow); if (_viewsFromRow == fromRow && _viewsTillRow == tillRow && _viewsForWidth == width()) { return; } _viewsFromRow = fromRow; _viewsTillRow = tillRow; _viewsForWidth = width(); const auto available = _viewsForWidth - padding.left() - padding.right(); const auto skipw = st::giftBoxGiftSkip.x(); const auto fullw = _perRow * (_single.width() + skipw) - skipw; const auto left = padding.left() + (available - fullw) / 2; const auto oneh = _single.height() + st::giftBoxGiftSkip.y(); const auto mode = GiftButton::Mode::Minimal; auto x = left; auto y = vskip + fromRow * oneh; auto views = std::vector(); views.reserve((tillRow - fromRow) * _perRow); const auto add = [&](int index) { const auto already = ranges::find(_views, index, &View::entry); if (already != end(_views)) { views.push_back(base::take(*already)); return; } const auto &descriptor = _entries[index].descriptor; const auto callback = [=] { showGift(index); }; const auto unused = ranges::find_if(_views, [&](const View &v) { return v.button && ((v.entry < fromRow * _perRow) || (v.entry >= tillRow * _perRow)); }); if (unused != end(_views)) { views.push_back(base::take(*unused)); views.back().entry = index; } else { auto button = std::make_unique(this, &_delegate); button->show(); views.push_back({ .button = std::move(button), .entry = index, }); } views.back().button->setDescriptor(descriptor, mode); views.back().button->setClickedCallback(callback); }; for (auto j = fromRow; j != tillRow; ++j) { for (auto i = 0; i != _perRow; ++i) { const auto index = j * _perRow + i; if (index >= _entries.size()) { break; } add(index); views.back().button->setGeometry( QRect(QPoint(x, y), _single), _delegate.buttonExtend()); x += _single.width() + skipw; } x = left; y += oneh; } std::swap(_views, views); } void InnerWidget::showGift(int index) { _window->show(Box( ::Settings::UserStarGiftBox, _window, _user, _entries[index].gift)); } int InnerWidget::resizeGetHeight(int width) { const auto count = int(_entries.size()); const auto padding = st::giftBoxPadding; const auto available = width - padding.left() - padding.right(); const auto skipw = st::giftBoxGiftSkip.x(); _perRow = std::min( (available + skipw) / (_singleMin.width() + skipw), count); if (!_perRow) { return 0; } const auto singlew = std::min( ((available + skipw) / _perRow) - skipw, 2 * _singleMin.width()); Assert(singlew >= _singleMin.width()); const auto singleh = _singleMin.height(); _single = QSize(singlew, singleh); const auto rows = (count + _perRow - 1) / _perRow; const auto skiph = st::giftBoxGiftSkip.y(); auto result = padding.bottom() * 2 + rows * (singleh + skiph) - skiph; const auto margin = st::giftListAboutMargin; _about->resizeToWidth(width - margin.left() - margin.right()); _about->moveToLeft(margin.left(), result + margin.top()); result += margin.top() + _about->height() + margin.bottom(); return result; } void InnerWidget::saveState(not_null memento) { auto state = std::make_unique(); memento->setListState(std::move(state)); } void InnerWidget::restoreState(not_null memento) { if (const auto state = memento->listState()) { } } Memento::Memento(not_null user) : ContentMemento(user, nullptr, PeerId()) { } Section Memento::section() const { return Section(Section::Type::PeerGifts); } not_null Memento::user() const { return peer()->asUser(); } object_ptr Memento::createWidget( QWidget *parent, not_null controller, const QRect &geometry) { auto result = object_ptr(parent, controller, user()); result->setInternalState(geometry, this); return result; } void Memento::setListState(std::unique_ptr state) { _listState = std::move(state); } std::unique_ptr Memento::listState() { return std::move(_listState); } Memento::~Memento() = default; Widget::Widget( QWidget *parent, not_null controller, not_null user) : ContentWidget(parent, controller) { _inner = setInnerWidget(object_ptr( this, controller, user)); } rpl::producer Widget::title() { return tr::lng_peer_gifts_title(); } not_null Widget::user() const { return _inner->user(); } bool Widget::showInternal(not_null memento) { if (!controller()->validateMementoPeer(memento)) { return false; } if (auto similarMemento = dynamic_cast(memento.get())) { if (similarMemento->user() == user()) { restoreState(similarMemento); return true; } } return false; } void Widget::setInternalState( const QRect &geometry, not_null memento) { setGeometry(geometry); Ui::SendPendingMoveResizeEvents(this); restoreState(memento); } std::shared_ptr Widget::doCreateMemento() { auto result = std::make_shared(user()); saveState(result.get()); return result; } void Widget::saveState(not_null memento) { memento->setScrollTop(scrollTopSave()); _inner->saveState(memento); } void Widget::restoreState(not_null memento) { _inner->restoreState(memento); scrollTopRestore(memento->scrollTop()); } } // namespace Info::PeerGifts