diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 19f47d29ed..fee73590f6 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -884,6 +884,8 @@ PRIVATE info/profile/info_profile_widget.h info/settings/info_settings_widget.cpp info/settings/info_settings_widget.h + info/statistics/info_statistics_widget.cpp + info/statistics/info_statistics_widget.h info/stories/info_stories_inner_widget.cpp info/stories/info_stories_inner_widget.h info/stories/info_stories_provider.cpp @@ -1281,8 +1283,6 @@ PRIVATE settings/settings_type.h settings/settings_websites.cpp settings/settings_websites.h - statistics/statistics_box.cpp - statistics/statistics_box.h storage/details/storage_file_utilities.cpp storage/details/storage_file_utilities.h storage/details/storage_settings_scheme.cpp diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index 82eb6f8eb3..40f3218637 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -106,6 +106,7 @@ public: Downloads, Stories, PollResults, + Statistics, }; using SettingsType = ::Settings::Type; using MediaType = Storage::SharedMediaType; diff --git a/Telegram/SourceFiles/statistics/statistics_box.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp similarity index 73% rename from Telegram/SourceFiles/statistics/statistics_box.cpp rename to Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp index 6b76fb0a0c..6be765c521 100644 --- a/Telegram/SourceFiles/statistics/statistics_box.cpp +++ b/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp @@ -5,10 +5,12 @@ 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 "statistics/statistics_box.h" +#include "info/statistics/info_statistics_widget.h" #include "api/api_statistics.h" #include "data/data_peer.h" +#include "info/info_controller.h" +#include "info/info_memento.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" #include "main/main_session.h" @@ -19,11 +21,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/rect.h" #include "ui/toast/toast.h" +#include "ui/widgets/scroll_area.h" #include "ui/wrap/slide_wrap.h" #include "styles/style_boxes.h" #include "styles/style_settings.h" #include "styles/style_statistics.h" +namespace Info::Statistics { namespace { struct Descriptor final { @@ -99,36 +103,37 @@ void ProcessChart( } void FillStatistic( - not_null box, + not_null content, const Descriptor &descriptor, const AnyStats &stats) { using Type = Statistic::ChartViewType; const auto &padding = st::statisticsChartEntryPadding; - const auto &m = st::boxRowPadding; + const auto &m = st::statisticsLayerMargins; const auto addSkip = [&](not_null c) { - Settings::AddSkip(c, padding.bottom()); - Settings::AddDivider(c); - Settings::AddSkip(c, padding.top()); + ::Settings::AddSkip(c, padding.bottom()); + ::Settings::AddDivider(c); + ::Settings::AddSkip(c, padding.top()); }; const auto addChart = [&]( const Data::StatisticalGraph &graphData, rpl::producer &&title, Statistic::ChartViewType type) { - const auto wrap = box->addRow( + const auto wrap = content->add( object_ptr>( - box, - object_ptr(box)), - {}); + content, + object_ptr(content))); ProcessChart( descriptor, wrap, - wrap->entity()->add(object_ptr(box), m), + wrap->entity()->add( + object_ptr(content), + m), graphData, std::move(title), type); addSkip(wrap->entity()); }; - addSkip(box->verticalLayout()); + addSkip(content); if (const auto s = stats.channel) { addChart( s.memberCountGraph, @@ -203,24 +208,27 @@ void FillStatistic( } void FillLoading( - not_null box, - rpl::producer toggleOn) { - const auto emptyWrap = box->verticalLayout()->add( + not_null container, + rpl::producer toggleOn, + rpl::producer<> showFinished) { + const auto emptyWrap = container->add( object_ptr>( - box->verticalLayout(), - object_ptr(box->verticalLayout()))); + container, + object_ptr(container))); emptyWrap->toggleOn(std::move(toggleOn), anim::type::instant); const auto content = emptyWrap->entity(); - auto icon = Settings::CreateLottieIcon( + auto icon = ::Settings::CreateLottieIcon( content, { .name = u"stats"_q, .sizeOverride = Size(st::changePhoneIconSize) }, st::settingsBlockedListIconPadding); - content->add(std::move(icon.widget)); - box->setShowFinishedCallback([animate = std::move(icon.animate)] { + ( + std::move(showFinished) | rpl::take(1) + ) | rpl::start_with_next([animate = std::move(icon.animate)] { animate(anim::repeat::loop); - }); + }, icon.widget->lifetime()); + content->add(std::move(icon.widget)); content->add( object_ptr>( @@ -240,10 +248,12 @@ void FillLoading( st::statisticsLoadingSubtext)), st::changePhoneDescriptionPadding + st::boxRowPadding); - Settings::AddSkip(content, st::settingsBlockedListIconPadding.top()); + ::Settings::AddSkip(content, st::settingsBlockedListIconPadding.top()); } -void FillOverview(not_null box, const AnyStats &stats) { +void FillOverview( + not_null content, + const AnyStats &stats) { using Value = Data::StatisticalValue; const auto &channel = stats.channel; @@ -251,9 +261,11 @@ void FillOverview(not_null box, const AnyStats &stats) { const auto startDate = channel ? channel.startDate : supergroup.startDate; const auto endDate = channel ? channel.endDate : supergroup.endDate; - Settings::AddSkip(box->verticalLayout()); + ::Settings::AddSkip(content); { - const auto header = box->addRow(object_ptr(box)); + const auto header = content->add( + object_ptr(content), + st::statisticsLayerMargins); header->resize(header->width(), st::statisticsChartHeaderHeight); header->setTitle(tr::lng_stats_overview_title(tr::now)); const auto formatter = u"MMM d"_q; @@ -265,7 +277,7 @@ void FillOverview(not_null box, const AnyStats &stats) { + ' ' + QLocale().toString(to.date(), formatter)); } - Settings::AddSkip(box->verticalLayout()); + ::Settings::AddSkip(content); struct Second final { QColor color; @@ -286,7 +298,9 @@ void FillOverview(not_null box, const AnyStats &stats) { }; }; - const auto container = box->addRow(object_ptr(box)); + const auto container = content->add( + object_ptr(content), + st::statisticsLayerMargins); const auto addPrimary = [&](const Value &v) { return Ui::CreateChild( @@ -397,33 +411,98 @@ void FillOverview(not_null box, const AnyStats &stats) { } // namespace -void StatisticsBox(not_null box, not_null peer) { - box->setTitle(tr::lng_stats_title()); - const auto loaded = box->lifetime().make_state>(); +Memento::Memento(not_null controller) +: Memento(controller->peer()) { +} + +Memento::Memento(not_null peer) +: ContentMemento(peer, nullptr, {}) { +} + +Memento::~Memento() = default; + +Section Memento::section() const { + return Section(Section::Type::Statistics); +} + +object_ptr Memento::createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) { + auto result = object_ptr(parent, controller); + return result; +} + +Widget::Widget( + QWidget *parent, + not_null controller) +: ContentWidget(parent, controller) { + const auto peer = controller->peer(); + if (!peer) { + return; + } + const auto inner = setInnerWidget(object_ptr(this)); + auto &lifetime = inner->lifetime(); + const auto loaded = lifetime.make_state>(); FillLoading( - box, - loaded->events_starting_with(false) | rpl::map(!rpl::mappers::_1)); + inner, + loaded->events_starting_with(false) | rpl::map(!rpl::mappers::_1), + _showFinished.events()); const auto descriptor = Descriptor{ peer, - box->lifetime().make_state(&peer->session().api()), - box->uiShow()->toastParent(), + lifetime.make_state(&peer->session().api()), + controller->uiShow()->toastParent(), }; - descriptor.api->request( - descriptor.peer - ) | rpl::start_with_done([=] { - const auto anyStats = AnyStats{ - descriptor.api->channelStats(), - descriptor.api->supergroupStats(), - }; - if (!anyStats.channel && !anyStats.supergroup) { - return; - } - FillOverview(box, anyStats); - FillStatistic(box, descriptor, anyStats); - loaded->fire(true); - box->verticalLayout()->resizeToWidth(box->width()); - box->showChildren(); - }, box->lifetime()); + _showFinished.events( + ) | rpl::take(1) | rpl::start_with_next([=] { + descriptor.api->request( + descriptor.peer + ) | rpl::start_with_done([=] { + const auto anyStats = AnyStats{ + descriptor.api->channelStats(), + descriptor.api->supergroupStats(), + }; + if (!anyStats.channel && !anyStats.supergroup) { + return; + } + FillOverview(inner, anyStats); + FillStatistic(inner, descriptor, anyStats); + loaded->fire(true); + inner->resizeToWidth(width()); + inner->showChildren(); + }, inner->lifetime()); + }, lifetime); } + +bool Widget::showInternal(not_null memento) { + return false; +} + +rpl::producer Widget::title() { + return tr::lng_stats_title(); +} + +rpl::producer Widget::desiredShadowVisibility() const { + return rpl::single(true); +} + +void Widget::showFinished() { + _showFinished.fire({}); +} + +std::shared_ptr Widget::doCreateMemento() { + auto result = std::make_shared(controller()); + return result; +} + +std::shared_ptr Make(not_null peer) { + return std::make_shared( + std::vector>( + 1, + std::make_shared(peer))); +} + +} // namespace Info::Statistics + diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_widget.h b/Telegram/SourceFiles/info/statistics/info_statistics_widget.h new file mode 100644 index 0000000000..75a9b00cd4 --- /dev/null +++ b/Telegram/SourceFiles/info/statistics/info_statistics_widget.h @@ -0,0 +1,47 @@ +/* +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 +*/ +#pragma once + +#include "info/info_content_widget.h" + +namespace Info::Statistics { + +class Memento final : public ContentMemento { +public: + Memento(not_null controller); + Memento(not_null peer); + ~Memento(); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) override; + + Section section() const override; + +}; + +class Widget final : public ContentWidget { +public: + Widget(QWidget *parent, not_null controller); + + bool showInternal(not_null memento) override; + rpl::producer title() override; + rpl::producer desiredShadowVisibility() const override; + void showFinished() override; + +private: + std::shared_ptr doCreateMemento() override; + + rpl::event_stream<> _showFinished; + +}; + +[[nodiscard]] std::shared_ptr Make(not_null peer); + +} // namespace Info::Statistics diff --git a/Telegram/SourceFiles/statistics/chart_widget.cpp b/Telegram/SourceFiles/statistics/chart_widget.cpp index ad4b59ef60..b902f2a866 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.cpp +++ b/Telegram/SourceFiles/statistics/chart_widget.cpp @@ -265,6 +265,7 @@ ChartWidget::Footer::Footer(not_null parent) if (s.isNull()) { return; } + const auto was = xPercentageLimits(); const auto w = float64(st::statisticsChartFooterSideWidth); _width = s.width() - w; _widthBetweenSides = s.width() - w * 2.; @@ -272,6 +273,9 @@ ChartWidget::Footer::Footer(not_null parent) s - QSize(0, st::statisticsChartLineWidth * 2), st::boxRadius); _frame = _mask; + if (_widthBetweenSides && was.max) { + setXPercentageLimits(was); + } prepareCache(s.height()); }, lifetime()); @@ -361,9 +365,11 @@ ChartWidget::Footer::Footer(not_null parent) Limits ChartWidget::Footer::xPercentageLimits() const { return { - .min = _leftSide.min / _widthBetweenSides, - .max = (_rightSide.min - st::statisticsChartFooterSideWidth) - / _widthBetweenSides, + .min = _widthBetweenSides ? _leftSide.min / _widthBetweenSides : 0., + .max = _widthBetweenSides + ? (_rightSide.min - st::statisticsChartFooterSideWidth) + / _widthBetweenSides + : 0., }; } diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index 661320c36b..bf5fa1f97a 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -10,6 +10,8 @@ using "ui/basic.style"; using "window/window.style"; using "ui/widgets/widgets.style"; +statisticsLayerMargins: margins(20px, 0px, 20px, 0px); + statisticsChartHeight: 150px; statisticsChartEntryPadding: margins(0px, 8px, 0px, 8px); diff --git a/Telegram/SourceFiles/statistics/statistics_box.h b/Telegram/SourceFiles/statistics/statistics_box.h deleted file mode 100644 index 5578cb3bc3..0000000000 --- a/Telegram/SourceFiles/statistics/statistics_box.h +++ /dev/null @@ -1,16 +0,0 @@ -/* -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 -*/ -#pragma once - -class PeerData; - -namespace Ui { -class GenericBox; -} // namespace Ui - -void StatisticsBox(not_null box, not_null peer); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 052c2484fa..7102cd7f7d 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_memento.h" #include "info/info_controller.h" #include "info/profile/info_profile_values.h" +#include "info/statistics/info_statistics_widget.h" #include "info/stories/info_stories_widget.h" #include "data/notify/data_notify_settings.h" #include "data/data_changes.h" @@ -85,7 +86,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "export/export_manager.h" #include "boxes/peers/edit_peer_info_box.h" -#include "statistics/statistics_box.h" #include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -1003,10 +1003,13 @@ void Filler::addViewStatistics() { #ifdef _DEBUG if (const auto channel = _peer->asChannel()) { if (channel->flags() & ChannelDataFlag::CanGetStatistics) { - const auto navigation = _controller; + const auto controller = _controller; + const auto weak = base::make_weak(_thread); const auto peer = _peer; _addAction(tr::lng_stats_title(tr::now), [=] { - navigation->show(Box(StatisticsBox, peer)); + if (const auto strong = weak.get()) { + controller->showSection(Info::Statistics::Make(peer)); + } }, nullptr); } }