Add channel gifts filter.

This commit is contained in:
John Preston 2025-01-21 22:06:17 +04:00
parent addd37fb1f
commit ec69d557dc
4 changed files with 163 additions and 19 deletions

View file

@ -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";

View file

@ -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();
}
}

View file

@ -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*> controller,
not_null<PeerData*> peer);
not_null<PeerData*> peer,
rpl::producer<Filter> filter);
[[nodiscard]] not_null<PeerData*> peer() const {
return _peer;
@ -65,6 +70,9 @@ public:
[[nodiscard]] rpl::producer<bool> notifyEnabled() const {
return _notifyEnabled.events();
}
[[nodiscard]] rpl::producer<> scrollToTop() const {
return _scrollToTop.events();
}
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> 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::SessionController*> _window;
rpl::variable<Filter> _filter;
Delegate _delegate;
not_null<Controller*> _controller;
std::unique_ptr<Ui::FlatLabel> _about;
const not_null<PeerData*> _peer;
std::vector<Entry> _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<bool> _notifyEnabled;
std::vector<View> _views;
@ -122,22 +134,13 @@ private:
InnerWidget::InnerWidget(
QWidget *parent,
not_null<Controller*> controller,
not_null<PeerData*> peer)
not_null<PeerData*> peer,
rpl::producer<Filter> 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<Ui::FlatLabel>(
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<Ui::FlatLabel>(
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<PeerData*> peer)
: ContentWidget(parent, controller) {
_inner = setInnerWidget(
object_ptr<InnerWidget>(this, controller, peer));
object_ptr<InnerWidget>(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<void(Filter&)> 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<QString> Widget::title() {
return tr::lng_peer_gifts_title();
}

View file

@ -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*> memento);
void fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) override;
rpl::producer<QString> title() override;
rpl::producer<bool> desiredBottomShadowVisibility() override;
@ -82,6 +95,7 @@ private:
InnerWidget *_inner = nullptr;
QPointer<Ui::SlideWrap<Ui::RpWidget>> _pinnedToBottom;
rpl::variable<bool> _hasPinnedToBottom;
rpl::variable<Filter> _filter;
bool _shown = false;
};