From ec69d557dc56c29895abc8defca633ed9b54f2e7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 21 Jan 2025 22:06:17 +0400 Subject: [PATCH] Add channel gifts filter. --- Telegram/Resources/langs/lang.strings | 7 + .../SourceFiles/info/info_wrap_widget.cpp | 5 + .../peer_gifts/info_peer_gifts_widget.cpp | 156 +++++++++++++++--- .../info/peer_gifts/info_peer_gifts_widget.h | 14 ++ 4 files changed, 163 insertions(+), 19 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2d277f91e..6318aecd9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2131,6 +2131,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings."; "lng_peer_gifts_notify" = "Notify About New Gifts"; "lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift."; +"lng_peer_gifts_filter_by_value" = "Sort by Value"; +"lng_peer_gifts_filter_by_date" = "Sort by Date"; +"lng_peer_gifts_filter_unlimited" = "Unlimited"; +"lng_peer_gifts_filter_limited" = "Limited"; +"lng_peer_gifts_filter_unique" = "Unique"; +"lng_peer_gifts_filter_saved" = "Displayed"; +"lng_peer_gifts_filter_unsaved" = "Hidden"; "lng_premium_gift_duration_months#one" = "for {count} month"; "lng_premium_gift_duration_months#other" = "for {count} months"; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index faa5c4835..2e81fe681 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -442,6 +442,11 @@ void WrapWidget::setupTopBarMenuToggle() { addTopBarMenuButton(); } }, _topBar->lifetime()); + } else if (section.type() == Section::Type::PeerGifts + && key.peer() + && key.peer()->isChannel() + && key.peer()->canManageGifts()) { + addTopBarMenuButton(); } } diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 9394aeeda..b3a33fef7 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -16,9 +16,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_controller.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/widgets/box_content_divider.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" +#include "ui/widgets/scroll_area.h" #include "ui/wrap/slide_wrap.h" #include "ui/ui_utility.h" #include "lang/lang_keys.h" @@ -28,6 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits_graphics.h" #include "styles/style_info.h" #include "styles/style_layers.h" // boxRadius +#include "styles/style_media_player.h" // mediaPlayerMenuCheck +#include "styles/style_menu_icons.h" #include "styles/style_credits.h" // giftBoxPadding namespace Info::PeerGifts { @@ -57,7 +61,8 @@ public: InnerWidget( QWidget *parent, not_null controller, - not_null peer); + not_null peer, + rpl::producer filter); [[nodiscard]] not_null peer() const { return _peer; @@ -65,6 +70,9 @@ public: [[nodiscard]] rpl::producer notifyEnabled() const { return _notifyEnabled.events(); } + [[nodiscard]] rpl::producer<> scrollToTop() const { + return _scrollToTop.events(); + } void saveState(not_null memento); void restoreState(not_null memento); @@ -89,21 +97,25 @@ private: void refreshButtons(); void validateButtons(); void showGift(int index); + void refreshAbout(); int resizeGetHeight(int width) override; const not_null _window; + rpl::variable _filter; Delegate _delegate; not_null _controller; std::unique_ptr _about; const not_null _peer; std::vector _entries; int _totalCount = 0; + rpl::event_stream<> _scrollToTop; MTP::Sender _api; mtpRequestId _loadMoreRequestId = 0; QString _offset; bool _allLoaded = false; + bool _reloading = false; rpl::event_stream _notifyEnabled; std::vector _views; @@ -122,22 +134,13 @@ private: InnerWidget::InnerWidget( QWidget *parent, not_null controller, - not_null peer) + not_null peer, + rpl::producer filter) : BoxContentDivider(parent) , _window(controller->parentController()) +, _filter(std::move(filter)) , _delegate(_window, GiftButtonMode::Minimal) , _controller(controller) -, _about((!peer->isSelf() && peer->canManageGifts()) - ? nullptr - : std::make_unique( - this, - (peer->isSelf() - ? tr::lng_peer_gifts_about_mine(Ui::Text::RichLangValue) - : tr::lng_peer_gifts_about( - lt_user, - rpl::single(Ui::Text::Bold(peer->shortName())), - Ui::Text::RichLangValue)), - st::giftListAbout)) , _peer(peer) , _totalCount(_peer->peerGiftsCount()) , _api(&_peer->session().mtp()) { @@ -146,6 +149,14 @@ InnerWidget::InnerWidget( if (peer->canManageGifts()) { subscribeToUpdates(); } + + _filter.value() | rpl::start_with_next([=] { + _reloading = true; + _api.request(base::take(_loadMoreRequestId)).cancel(); + _allLoaded = false; + refreshAbout(); + loadMore(); + }, lifetime()); } void InnerWidget::subscribeToUpdates() { @@ -221,11 +232,16 @@ void InnerWidget::loadMore() { return; } using Flag = MTPpayments_GetSavedStarGifts::Flag; - const auto withUnsaved = _peer->canManageGifts(); + const auto filter = _filter.current(); _loadMoreRequestId = _api.request(MTPpayments_GetSavedStarGifts( - MTP_flags(withUnsaved ? Flag() : Flag::f_exclude_unsaved), + MTP_flags((filter.sortByValue ? Flag::f_sort_by_value : Flag()) + | (filter.skipLimited ? Flag::f_exclude_limited : Flag()) + | (filter.skipUnlimited ? Flag::f_exclude_unlimited : Flag()) + | (filter.skipUnique ? Flag::f_exclude_unique : Flag()) + | (filter.skipSaved ? Flag::f_exclude_saved : Flag()) + | (filter.skipUnsaved ? Flag::f_exclude_unsaved : Flag())), _peer->input, - MTP_string(_offset), + MTP_string(_reloading ? QString() : _offset), MTP_int(kPerPage) )).done([=](const MTPpayments_SavedStarGifts &result) { _loadMoreRequestId = 0; @@ -244,6 +260,10 @@ void InnerWidget::loadMore() { owner->processUsers(data.vusers()); owner->processChats(data.vchats()); + if (base::take(_reloading)) { + _entries.clear(); + _views.clear(); + } _entries.reserve(_entries.size() + data.vgifts().v.size()); for (const auto &gift : data.vgifts().v) { if (auto parsed = Api::FromTL(_peer, gift)) { @@ -255,6 +275,7 @@ void InnerWidget::loadMore() { } } refreshButtons(); + refreshAbout(); }).fail([=] { _loadMoreRequestId = 0; _allLoaded = true; @@ -353,6 +374,27 @@ void InnerWidget::showGift(int index) { _entries[index].gift)); } +void InnerWidget::refreshAbout() { + if (!_peer->isSelf() && _peer->canManageGifts() && !_entries.empty()) { + if (_about) { + _about = nullptr; + resizeToWidth(width()); + } + } else if (!_about) { + _about = std::make_unique( + this, + (_peer->isSelf() + ? tr::lng_peer_gifts_about_mine(Ui::Text::RichLangValue) + : tr::lng_peer_gifts_about( + lt_user, + rpl::single(Ui::Text::Bold(_peer->shortName())), + Ui::Text::RichLangValue)), + st::giftListAbout); + _about->show(); + resizeToWidth(width()); + } +} + int InnerWidget::resizeGetHeight(int width) { const auto count = int(_entries.size()); const auto padding = st::giftBoxPadding; @@ -360,7 +402,7 @@ int InnerWidget::resizeGetHeight(int width) { const auto skipw = st::giftBoxGiftSkip.x(); _perRow = std::min( (available + skipw) / (_singleMin.width() + skipw), - count); + std::max(count, 1)); if (!_perRow) { return 0; } @@ -374,7 +416,9 @@ int InnerWidget::resizeGetHeight(int width) { const auto rows = (count + _perRow - 1) / _perRow; const auto skiph = st::giftBoxGiftSkip.y(); - auto result = padding.bottom() * 2 + rows * (singleh + skiph) - skiph; + auto result = rows + ? (padding.bottom() * 2 + rows * (singleh + skiph) - skiph) + : 0; if (const auto about = _about.get()) { const auto margin = st::giftListAboutMargin; @@ -430,11 +474,14 @@ Widget::Widget( not_null peer) : ContentWidget(parent, controller) { _inner = setInnerWidget( - object_ptr(this, controller, peer)); + object_ptr(this, controller, peer, _filter.value())); _inner->notifyEnabled( ) | rpl::take(1) | rpl::start_with_next([=](bool enabled) { setupNotifyCheckbox(enabled); }, _inner->lifetime()); + _inner->scrollToTop() | rpl::start_with_next([=] { + scrollTo({ 0, 0 }); + }, _inner->lifetime()); } void Widget::showFinished() { @@ -510,6 +557,77 @@ void Widget::setupNotifyCheckbox(bool enabled) { _hasPinnedToBottom = true; } +void Widget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) { + const auto filter = _filter.current(); + const auto change = [=](Fn update) { + auto now = _filter.current(); + update(now); + _filter = now; + }; + + if (filter.sortByValue) { + addAction(tr::lng_peer_gifts_filter_by_date(tr::now), [=] { + change([](Filter &filter) { filter.sortByValue = false; }); + }, &st::menuIconSchedule); + } else { + addAction(tr::lng_peer_gifts_filter_by_value(tr::now), [=] { + change([](Filter &filter) { filter.sortByValue = true; }); + }, &st::menuIconEarn); + } + + addAction({ .isSeparator = true }); + + addAction(tr::lng_peer_gifts_filter_unlimited(tr::now), [=] { + change([](Filter &filter) { + filter.skipUnlimited = !filter.skipUnlimited; + if (filter.skipUnlimited + && filter.skipLimited + && filter.skipUnique) { + filter.skipLimited = false; + } + }); + }, filter.skipUnlimited ? nullptr : &st::mediaPlayerMenuCheck); + addAction(tr::lng_peer_gifts_filter_limited(tr::now), [=] { + change([](Filter &filter) { + filter.skipLimited = !filter.skipLimited; + if (filter.skipUnlimited + && filter.skipLimited + && filter.skipUnique) { + filter.skipUnlimited = false; + } + }); + }, filter.skipLimited ? nullptr : &st::mediaPlayerMenuCheck); + addAction(tr::lng_peer_gifts_filter_unique(tr::now), [=] { + change([](Filter &filter) { + filter.skipUnique = !filter.skipUnique; + if (filter.skipUnlimited + && filter.skipLimited + && filter.skipUnique) { + filter.skipUnlimited = false; + } + }); + }, filter.skipUnique ? nullptr : &st::mediaPlayerMenuCheck); + + addAction({ .isSeparator = true }); + + addAction(tr::lng_peer_gifts_filter_saved(tr::now), [=] { + change([](Filter &filter) { + filter.skipSaved = !filter.skipSaved; + if (filter.skipSaved && filter.skipUnsaved) { + filter.skipUnsaved = false; + } + }); + }, filter.skipSaved ? nullptr : &st::mediaPlayerMenuCheck); + addAction(tr::lng_peer_gifts_filter_unsaved(tr::now), [=] { + change([](Filter &filter) { + filter.skipUnsaved = !filter.skipUnsaved; + if (filter.skipSaved && filter.skipUnsaved) { + filter.skipSaved = false; + } + }); + }, filter.skipUnsaved ? nullptr : &st::mediaPlayerMenuCheck); +} + rpl::producer Widget::title() { return tr::lng_peer_gifts_title(); } diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.h index c482fd473..262465801 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.h @@ -26,6 +26,17 @@ struct ListState { QString offset; }; +struct Filter { + bool sortByValue : 1 = false; + bool skipUnlimited : 1 = false; + bool skipLimited : 1 = false; + bool skipUnique : 1 = false; + bool skipSaved : 1 = false; + bool skipUnsaved : 1 = false; + + friend inline bool operator==(Filter, Filter) = default; +}; + class InnerWidget; class Memento final : public ContentMemento { @@ -65,6 +76,8 @@ public: const QRect &geometry, not_null memento); + void fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) override; + rpl::producer title() override; rpl::producer desiredBottomShadowVisibility() override; @@ -82,6 +95,7 @@ private: InnerWidget *_inner = nullptr; QPointer> _pinnedToBottom; rpl::variable _hasPinnedToBottom; + rpl::variable _filter; bool _shown = false; };