From 154fe63b4390dbb88b58c5e6ddecd48441bbe5d5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 20 May 2024 19:13:22 +0300 Subject: [PATCH] Implemented list of credit history entries. --- .../info_statistics_list_controllers.cpp | 306 ++++++++++++++++++ .../info_statistics_list_controllers.h | 11 + Telegram/SourceFiles/ui/effects/credits.style | 2 + 3 files changed, 319 insertions(+) diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp index 1ed6d7884..6d6abdec4 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp @@ -7,9 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/statistics/info_statistics_list_controllers.h" +#include "api/api_credits.h" #include "api/api_statistics.h" #include "boxes/peer_list_controllers.h" #include "data/data_channel.h" +#include "data/data_credits.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_user.h" @@ -26,7 +28,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "styles/style_credits.h" #include "styles/style_dialogs.h" // dialogsStoriesFull. +#include "styles/style_intro.h" // introFragmentIcon. +#include "styles/style_layers.h" // boxRowPadding. #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "styles/style_statistics.h" @@ -113,6 +118,15 @@ struct BoostsDescriptor final { not_null peer; }; +struct CreditsDescriptor final { + Data::CreditsStatusSlice firstSlice; + Fn entryClickedCallback; + not_null peer; + not_null creditIcon; + bool in = false; + bool out = false; +}; + class PeerListRowWithFullId : public PeerListRow { public: PeerListRowWithFullId( @@ -692,6 +706,254 @@ rpl::producer BoostsController::totalBoostsValue() const { return _totalBoosts.value(); } +class CreditsRow final : public PeerListRow { +public: + struct Descriptor final { + Data::CreditsHistoryEntry entry; + not_null creditIcon; + int rowHeight = 0; + }; + + CreditsRow(not_null peer, const Descriptor &descriptor); + CreditsRow(const Descriptor &descriptor); + + [[nodiscard]] const Data::CreditsHistoryEntry &entry() const; + [[nodiscard]] QString generateName() override; + + [[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback( + bool forceRound) override; + + QSize rightActionSize() const override; + QMargins rightActionMargins() const override; + bool rightActionDisabled() const override; + void rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) override; + +private: + void init(); + + const Data::CreditsHistoryEntry _entry; + not_null const _creditIcon; + const int _rowHeight; + Ui::EmptyUserpic _userpic; + + Ui::Text::String _rightText; +}; + +CreditsRow::CreditsRow(not_null peer, const Descriptor &descriptor) +: PeerListRow(peer, UniqueRowIdFromString(descriptor.entry.id)) +, _entry(descriptor.entry) +, _creditIcon(descriptor.creditIcon) +, _rowHeight(descriptor.rowHeight) +, _userpic(Ui::EmptyUserpic::UserpicColor(0), QString()) { + init(); +} + +CreditsRow::CreditsRow(const Descriptor &descriptor) +: PeerListRow(UniqueRowIdFromString(descriptor.entry.id)) +, _entry(descriptor.entry) +, _creditIcon(descriptor.creditIcon) +, _rowHeight(descriptor.rowHeight) +, _userpic( + [&]() -> Ui::EmptyUserpic::BgColors { + switch (descriptor.entry.peerType) { + case Data::CreditsHistoryEntry::PeerType::Peer: + return Ui::EmptyUserpic::UserpicColor(0); + case Data::CreditsHistoryEntry::PeerType::AppStore: + return { st::historyPeer7UserpicBg, st::historyPeer7UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::PlayMarket: + return { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 }; + case Data::CreditsHistoryEntry::PeerType::Fragment: + return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 }; + } + Unexpected("Unknown peer type."); + }(), + QString()) { + init(); +} + +void CreditsRow::init() { + PeerListRow::setCustomStatus(langDateTimeFull(_entry.date)); + { + constexpr auto kMinus = QChar(0x2212); + _rightText.setText( + st::semiboldTextStyle, + (PeerListRow::special() ? QChar('+') : kMinus) + + Lang::FormatCountDecimal(_entry.credits)); + } +} + +const Data::CreditsHistoryEntry &CreditsRow::entry() const { + return _entry; +} + +QString CreditsRow::generateName() { + return !PeerListRow::special() + ? PeerListRow::generateName() + : (_entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment) + ? tr::lng_bot_username_description1_link(tr::now) + : tr::lng_credits_summary_history_entry_inner_in(tr::now); +} + +PaintRoundImageCallback CreditsRow::generatePaintUserpicCallback(bool force) { + if (!PeerListRow::special()) { + return PeerListRow::generatePaintUserpicCallback(force); + } + return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { + _userpic.paintCircle(p, x, y, outerWidth, size); + using PeerType = Data::CreditsHistoryEntry::PeerType; + ((_entry.peerType == PeerType::AppStore) + ? st::sessionIconiPhone + : (_entry.peerType == PeerType::PlayMarket) + ? st::sessionIconAndroid + : st::introFragmentIcon).paintInCenter(p, { x, y, size, size }); + }; +} + +QSize CreditsRow::rightActionSize() const { + return QSize( + _rightText.maxWidth() + + (_creditIcon->width() / style::DevicePixelRatio()) + + st::creditsHistoryRightSkip + + _rightText.style()->font->spacew * 2, + _rowHeight); +} + +QMargins CreditsRow::rightActionMargins() const { + return QMargins(0, 0, st::boxRowPadding.right(), 0); +} + +bool CreditsRow::rightActionDisabled() const { + return true; +} + +void CreditsRow::rightActionPaint( + Painter &p, + int x, + int y, + int outerWidth, + bool selected, + bool actionSelected) { + const auto &font = _rightText.style()->font; + y += _rowHeight / 2; + p.setPen(PeerListRow::special() + ? st::boxTextFgGood + : st::menuIconAttentionColor); + x += st::creditsHistoryRightSkip; + _rightText.draw(p, Ui::Text::PaintContext{ + .position = QPoint(x, y - font->height / 2), + .availableWidth = outerWidth, + .outerWidth = outerWidth, + }); + x += _rightText.maxWidth() + font->spacew * 2; + p.drawImage( + x, + y -(_creditIcon->height() / style::DevicePixelRatio()) / 2, + *_creditIcon); +} + +class CreditsController final : public PeerListController { +public: + explicit CreditsController(CreditsDescriptor d); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void loadMoreRows() override; + + [[nodiscard]] bool skipRequest() const; + void requestNext(); + + [[nodiscard]] rpl::producer allLoadedValue() const; + +private: + void applySlice(const Data::CreditsStatusSlice &slice); + + const not_null _session; + Fn _entryClickedCallback; + not_null const _creditIcon; + + Api::CreditsHistory _api; + Data::CreditsStatusSlice _firstSlice; + Data::CreditsStatusSlice::OffsetToken _apiToken; + + rpl::variable _allLoaded = false; + bool _requesting = false; + +}; + +CreditsController::CreditsController(CreditsDescriptor d) +: _session(&d.peer->session()) +, _entryClickedCallback(std::move(d.entryClickedCallback)) +, _creditIcon(d.creditIcon) +, _api(d.peer, d.in, d.out) +, _firstSlice(std::move(d.firstSlice)) { + PeerListController::setStyleOverrides(&st::boostsListBox); +} + +Main::Session &CreditsController::session() const { + return *_session; +} + +bool CreditsController::skipRequest() const { + return _requesting || _allLoaded.current(); +} + +void CreditsController::requestNext() { + _requesting = true; + _api.request(_apiToken, [=](const Data::CreditsStatusSlice &s) { + _requesting = false; + applySlice(s); + }); +} + +void CreditsController::prepare() { + applySlice(base::take(_firstSlice)); + delegate()->peerListRefreshRows(); +} + +void CreditsController::loadMoreRows() { +} + +void CreditsController::applySlice(const Data::CreditsStatusSlice &slice) { + _allLoaded = slice.allLoaded; + _apiToken = slice.token; + + for (const auto &item : slice.list) { + auto row = [&] { + const auto descriptor = CreditsRow::Descriptor{ + .entry = item, + .creditIcon = _creditIcon, + .rowHeight = computeListSt().item.height, + }; + if (item.peerId) { + const auto peer = session().data().peer(item.peerId); + return std::make_unique(peer, descriptor); + } else { + return std::make_unique(descriptor); + } + }(); + delegate()->peerListAppendRow(std::move(row)); + } + delegate()->peerListRefreshRows(); +} + +void CreditsController::rowClicked(not_null row) { + if (_entryClickedCallback) { + _entryClickedCallback( + static_cast(row.get())->entry()); + } +} + +rpl::producer CreditsController::allLoadedValue() const { + return _allLoaded.value(); +} + } // namespace void AddPublicForwards( @@ -843,4 +1105,48 @@ void AddBoostsList( button->setClickedCallback(showMore); } +void AddCreditsHistoryList( + const Data::CreditsStatusSlice &firstSlice, + not_null container, + Fn callback, + not_null self, + not_null icon, + bool in, + bool out) { + struct State final { + State(CreditsDescriptor d) : controller(std::move(d)) { + } + PeerListContentDelegateSimple delegate; + CreditsController controller; + }; + auto d = CreditsDescriptor{ firstSlice, callback, self, icon, in, out }; + const auto state = container->lifetime().make_state(std::move(d)); + + state->delegate.setContent(container->add( + object_ptr(container, &state->controller))); + state->controller.setDelegate(&state->delegate); + + const auto wrap = container->add( + object_ptr>( + container, + object_ptr( + container, + tr::lng_stories_show_more(), + st::statisticsShowMoreButton)), + { 0, -st::settingsButton.padding.top(), 0, 0 }); + const auto button = wrap->entity(); + AddArrow(button); + + const auto showMore = [=] { + if (!state->controller.skipRequest()) { + state->controller.requestNext(); + container->resizeToWidth(container->width()); + } + }; + wrap->toggleOn( + state->controller.allLoadedValue() | rpl::map(!rpl::mappers::_1), + anim::type::instant); + button->setClickedCallback(showMore); +} + } // namespace Info::Statistics diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h index 2a2dfe93a..0e3d00019 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h +++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h @@ -16,6 +16,8 @@ class VerticalLayout; namespace Data { struct Boost; struct BoostsListSlice; +struct CreditsHistoryEntry; +struct CreditsStatusSlice; struct PublicForwardsSlice; struct RecentPostId; struct SupergroupStatistics; @@ -44,4 +46,13 @@ void AddBoostsList( not_null peer, rpl::producer title); +void AddCreditsHistoryList( + const Data::CreditsStatusSlice &firstSlice, + not_null container, + Fn entryClickedCallback, + not_null self, + not_null creditIcon, + bool in, + bool out); + } // namespace Info::Statistics diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 4c27ea541..355d0fc5b 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -22,3 +22,5 @@ creditsTopupButton: SettingsButton(settingsButton) { creditsTopupPrice: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; } + +creditsHistoryRightSkip: 10px;