diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7053293e93..98e41d30e5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4083,6 +4083,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stats_overview_message_public_share#one" = "{count} public share"; "lng_stats_overview_message_public_share#other" = "{count} public shares"; +"lng_stats_members_title" = "Top members"; +"lng_stats_admins_title" = "Top admins"; +"lng_stats_inviters_title" = "Top inviters"; +"lng_stats_member_messages#one" = "{count} message"; +"lng_stats_member_messages#other" = "{count} messages"; +"lng_stats_member_characters#one" = "{count} symbol per message"; +"lng_stats_member_characters#other" = "{count} symbols per message"; +"lng_stats_member_deletions#one" = "{count} deletions"; +"lng_stats_member_deletions#other" = "{count} deletions"; +"lng_stats_member_bans#one" = "{count} ban"; +"lng_stats_member_bans#other" = "{count} bans"; +"lng_stats_member_restrictions#one" = "{count} restriction"; +"lng_stats_member_restrictions#other" = "{count} restrictions"; +"lng_stats_member_invitations#one" = "{count} invitation"; +"lng_stats_member_invitations#other" = "{count} invitations"; + "lng_stats_recent_messages_title" = "Recent posts"; "lng_stats_recent_messages_views#one" = "{count} view"; "lng_stats_recent_messages_views#other" = "{count} views"; diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_public_forwards.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_public_forwards.cpp index 2197e46bed..13a264cba1 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_public_forwards.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_public_forwards.cpp @@ -11,16 +11,53 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_controllers.h" #include "data/data_channel.h" #include "data/data_session.h" +#include "data/data_user.h" #include "history/history_item.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "settings/settings_common.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "styles/style_settings.h" namespace Info::Statistics { namespace { +void AddSubsectionTitle( + not_null container, + rpl::producer title) { + const auto &subtitlePadding = st::settingsButton.padding; + ::Settings::AddSubsectionTitle( + container, + std::move(title), + { 0, -subtitlePadding.top(), 0, -subtitlePadding.bottom() }); +} + +[[nodiscard]] QString FormatText( + int value1, tr::phrase phrase1, + int value2, tr::phrase phrase2, + int value3, tr::phrase phrase3) { + const auto separator = u", "_q; + auto resultText = QString(); + if (value1 > 0) { + resultText += phrase1(tr::now, lt_count, value1); + } + if (value2 > 0) { + if (!resultText.isEmpty()) { + resultText += separator; + } + resultText += phrase2(tr::now, lt_count, value2); + } + if (value3 > 0) { + if (!resultText.isEmpty()) { + resultText += separator; + } + resultText += phrase3(tr::now, lt_count, value3); + } + return resultText; +} + struct Descriptor final { Api::PublicForwards::Slice firstSlice; Fn showPeerHistory; @@ -28,6 +65,12 @@ struct Descriptor final { FullMsgId contextId; }; +struct MembersDescriptor final { + not_null session; + Fn)> showPeerInfo; + Data::SupergroupStatistics data; +}; + class PeerListRowWithMsgId : public PeerListRow { public: using PeerListRow::PeerListRow; @@ -48,6 +91,103 @@ MsgId PeerListRowWithMsgId::msgId() const { return _msgId; } +class MembersController final : public PeerListController { +public: + MembersController(MembersDescriptor d); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void loadMoreRows() override; + + void setLimit(int limit); + +private: + void addRows(int from, int to); + + const not_null _session; + Fn)> _showPeerInfo; + Data::SupergroupStatistics _data; + int _limit = 0; + +}; + +MembersController::MembersController(MembersDescriptor d) +: _session(std::move(d.session)) +, _showPeerInfo(std::move(d.showPeerInfo)) +, _data(std::move(d.data)) { +} + +Main::Session &MembersController::session() const { + return *_session; +} + +void MembersController::setLimit(int limit) { + addRows(_limit, limit); + _limit = limit; +} + +void MembersController::addRows(int from, int to) { + const auto addRow = [&](UserId userId, QString text) { + const auto user = _session->data().user(userId); + auto row = std::make_unique(user); + row->setCustomStatus(std::move(text)); + delegate()->peerListAppendRow(std::move(row)); + }; + if (!_data.topSenders.empty()) { + for (auto i = from; i < to; i++) { + const auto &member = _data.topSenders[i]; + addRow( + member.userId, + FormatText( + member.sentMessageCount, + tr::lng_stats_member_messages, + member.averageCharacterCount, + tr::lng_stats_member_characters, + 0, + {})); + } + } else if (!_data.topAdministrators.empty()) { + for (auto i = from; i < to; i++) { + const auto &admin = _data.topAdministrators[i]; + addRow( + admin.userId, + FormatText( + admin.deletedMessageCount, + tr::lng_stats_member_deletions, + admin.bannedUserCount, + tr::lng_stats_member_bans, + admin.restrictedUserCount, + tr::lng_stats_member_restrictions)); + } + } else if (!_data.topInviters.empty()) { + for (auto i = from; i < to; i++) { + const auto &inviter = _data.topInviters[i]; + addRow( + inviter.userId, + FormatText( + inviter.addedMemberCount, + tr::lng_stats_member_invitations, + 0, + {}, + 0, + {})); + } + } +} + +void MembersController::prepare() { +} + +void MembersController::loadMoreRows() { +} + +void MembersController::rowClicked(not_null row) { + crl::on_main([=, peer = row->peer()] { + _showPeerInfo(peer); + }); +} + class PublicForwardsController final : public PeerListController { public: explicit PublicForwardsController(Descriptor d); @@ -57,8 +197,6 @@ public: void rowClicked(not_null row) override; void loadMoreRows() override; - [[nodiscard]] rpl::producer totalCountChanges() const; - private: bool appendRow(not_null peer, MsgId msgId); void applySlice(const Api::PublicForwards::Slice &slice); @@ -72,8 +210,6 @@ private: bool _allLoaded = false; - rpl::event_stream _totalCountChanges; - }; PublicForwardsController::PublicForwardsController(Descriptor d) @@ -105,7 +241,6 @@ void PublicForwardsController::applySlice( const Api::PublicForwards::Slice &slice) { _allLoaded = slice.allLoaded; _apiToken = slice.token; - _totalCountChanges.fire_copy(slice.total); for (const auto &item : slice.list) { if (const auto peer = session().data().peerLoaded(item.peer)) { @@ -153,10 +288,6 @@ bool PublicForwardsController::appendRow( return true; } -rpl::producer PublicForwardsController::totalCountChanges() const { - return _totalCountChanges.events(); -} - } // namespace void AddPublicForwards( @@ -183,13 +314,11 @@ void AddPublicForwards( }); if (const auto total = firstSliceHolder.firstSlice().total; total > 0) { - const auto &subtitlePadding = st::settingsButton.padding; - ::Settings::AddSubsectionTitle( + AddSubsectionTitle( container, tr::lng_stats_overview_message_public_share( lt_count_decimal, - rpl::single(total)), - { 0, -subtitlePadding.top(), 0, -subtitlePadding.bottom() }); + rpl::single(total))); } state->delegate.setContent(container->add( @@ -197,4 +326,66 @@ void AddPublicForwards( state->controller.setDelegate(&state->delegate); } +void AddMembersList( + Data::SupergroupStatistics data, + not_null container, + Fn)> showPeerInfo, + not_null peer, + rpl::producer title) { + if (!peer->isMegagroup()) { + return; + } + const auto max = !data.topSenders.empty() + ? data.topSenders.size() + : !data.topAdministrators.empty() + ? data.topAdministrators.size() + : !data.topInviters.empty() + ? data.topInviters.size() + : 0; + if (!max) { + return; + } + + constexpr auto kPerPage = 40; + struct State final { + State(MembersDescriptor d) : controller(std::move(d)) { + } + PeerListContentDelegateSimple delegate; + MembersController controller; + int limit = 0; + }; + auto d = MembersDescriptor{ + &peer->session(), + std::move(showPeerInfo), + std::move(data), + }; + const auto state = container->lifetime().make_state(std::move(d)); + + AddSubsectionTitle(container, std::move(title)); + + 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())), + { 0, -st::settingsButton.padding.top(), 0, 0 }); + const auto button = wrap->entity(); + + const auto showMore = [=] { + state->limit = std::min(int(max), state->limit + kPerPage); + state->controller.setLimit(state->limit); + if (state->limit == max) { + wrap->toggle(false, anim::type::instant); + } + container->resizeToWidth(container->width()); + }; + button->setClickedCallback(showMore); + showMore(); +} + } // namespace Info::Statistics diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_public_forwards.h b/Telegram/SourceFiles/info/statistics/info_statistics_public_forwards.h index 503e9d9ea1..d2aa31e1f9 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_public_forwards.h +++ b/Telegram/SourceFiles/info/statistics/info_statistics_public_forwards.h @@ -17,6 +17,10 @@ namespace Api { class MessageStatistics; } // namespace Api +namespace Data { +struct SupergroupStatistics; +} // namespace Data + namespace Info::Statistics { void AddPublicForwards( @@ -26,4 +30,11 @@ void AddPublicForwards( not_null peer, FullMsgId contextId); +void AddMembersList( + Data::SupergroupStatistics data, + not_null container, + Fn)> showPeerInfo, + not_null peer, + rpl::producer title); + } // namespace Info::Statistics diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp index f8eaa9688d..2662745d80 100644 --- a/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp @@ -609,12 +609,54 @@ Widget::Widget( FillOverview(inner, anyStats); FillStatistic(inner, descriptor, anyStats); - if (const auto channel = anyStats.channel) { + const auto &channel = anyStats.channel; + const auto &supergroup = anyStats.supergroup; + if (channel) { const auto showSection = [controller]( std::shared_ptr memento) { controller->showSection(std::move(memento)); }; FillRecentPosts(inner, descriptor, channel, showSection); + } else if (supergroup) { + const auto showPeerInfo = [controller]( + not_null peer) { + controller->showSection( + std::make_shared(peer)); + }; + const auto addSkip = [&]( + not_null c) { + ::Settings::AddSkip(c); + ::Settings::AddDivider(c); + ::Settings::AddSkip(c); + ::Settings::AddSkip(c); + }; + if (!supergroup.topSenders.empty()) { + AddMembersList( + { .topSenders = supergroup.topSenders }, + inner, + showPeerInfo, + descriptor.peer, + tr::lng_stats_members_title()); + } + if (!supergroup.topAdministrators.empty()) { + addSkip(inner); + AddMembersList( + { .topAdministrators + = supergroup.topAdministrators }, + inner, + showPeerInfo, + descriptor.peer, + tr::lng_stats_admins_title()); + } + if (!supergroup.topInviters.empty()) { + addSkip(inner); + AddMembersList( + { .topInviters = supergroup.topInviters }, + inner, + showPeerInfo, + descriptor.peer, + tr::lng_stats_inviters_title()); + } } finishLoading(); }, inner->lifetime());