Added initial support of recent posts to statistical info.

This commit is contained in:
23rd 2023-10-06 05:38:37 +03:00 committed by John Preston
parent fc3acff5d6
commit 8564e4d727
6 changed files with 378 additions and 17 deletions

View File

@ -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_recent_message.cpp
info/statistics/info_statistics_recent_message.h
info/statistics/info_statistics_widget.cpp
info/statistics/info_statistics_widget.h
info/stories/info_stories_inner_widget.cpp

View File

@ -4077,6 +4077,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stats_overview_group_mean_view_count" = "Viewing Members";
"lng_stats_overview_group_mean_post_count" = "Posting Members";
"lng_stats_recent_messages_title" = "Recent posts";
"lng_stats_recent_messages_views#one" = "{count} view";
"lng_stats_recent_messages_views#other" = "{count} views";
"lng_stats_recent_messages_shares#one" = "{count} share";
"lng_stats_recent_messages_shares#other" = "{count} shares";
"lng_stats_loading" = "Loading stats...";
"lng_stats_loading_subtext" = "Please wait a few moments while we generate your stats.";

View File

@ -0,0 +1,227 @@
/*
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
*/
#include "info/statistics/info_statistics_recent_message.h"
#include "core/ui_integration.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/view/history_view_item_preview.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/power_saving.h"
#include "ui/rect.h"
#include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_statistics.h"
namespace Info::Statistics {
namespace {
[[nodiscard]] QImage PreparePreviewImage(
QImage original,
ImageRoundRadius radius,
bool spoiler) {
if (original.width() * 10 < original.height()
|| original.height() * 10 < original.width()) {
return QImage();
}
const auto factor = style::DevicePixelRatio();
const auto size = st::statisticsRecentPostRowHeight * factor;
const auto scaled = original.scaled(
QSize(size, size),
Qt::KeepAspectRatioByExpanding,
Qt::FastTransformation);
auto square = scaled.copy(
(scaled.width() - size) / 2,
(scaled.height() - size) / 2,
size,
size
).convertToFormat(QImage::Format_ARGB32_Premultiplied);
if (spoiler) {
square = Images::BlurLargeImage(
std::move(square),
style::ConvertScale(3) * factor);
}
square = Images::Round(std::move(square), radius);
square.setDevicePixelRatio(factor);
return square;
}
} // namespace
MessagePreview::MessagePreview(
not_null<Ui::RpWidget*> parent,
not_null<HistoryItem*> item,
int views,
int shares)
: Ui::RpWidget(parent)
, _item(item)
, _date(
st::statisticsHeaderDatesTextStyle,
Ui::FormatDateTime(ItemDateTime(item)))
, _views(
st::statisticsDetailsPopupHeaderStyle,
tr::lng_stats_recent_messages_views(
tr::now,
lt_count_decimal,
views))
, _shares(
st::statisticsHeaderDatesTextStyle,
tr::lng_stats_recent_messages_shares(
tr::now,
lt_count_decimal,
shares))
, _viewsWidth(_views.maxWidth())
, _sharesWidth(_shares.maxWidth()) {
_text.setMarkedText(
st::statisticsDetailsPopupHeaderStyle,
_item->toPreview({ .generateImages = false }).text,
Ui::DialogTextOptions(),
Core::MarkedTextContext{
.session = &item->history()->session(),
.customEmojiRepaint = [=] { update(); },
});
processPreview(item);
}
void MessagePreview::processPreview(not_null<HistoryItem*> item) {
if (const auto media = item->media()) {
if (item->media()->hasSpoiler()) {
_spoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
update();
});
}
if (const auto photo = media->photo()) {
_photoMedia = photo->createMediaView();
_photoMedia->wanted(Data::PhotoSize::Large, item->fullId());
} else if (const auto document = media->document()) {
_documentMedia = document->createMediaView();
_documentMedia->thumbnailWanted(item->fullId());
}
}
const auto session = _photoMedia
? &_photoMedia->owner()->session()
: _documentMedia
? &_documentMedia->owner()->session()
: nullptr;
if (!session) {
return;
}
struct ThumbInfo final {
bool loaded = false;
Image *image = nullptr;
};
const auto computeThumbInfo = [=]() -> ThumbInfo {
using Size = Data::PhotoSize;
if (_documentMedia) {
return { true, _documentMedia->thumbnail() };
} else if (const auto large = _photoMedia->image(Size::Large)) {
return { true, large };
} else if (const auto thumbnail = _photoMedia->image(
Size::Thumbnail)) {
return { false, thumbnail };
} else if (const auto small = _photoMedia->image(Size::Small)) {
return { false, small };
} else {
return { false, _photoMedia->thumbnailInline() };
}
};
rpl::single(rpl::empty) | rpl::then(
session->downloaderTaskFinished()
) | rpl::start_with_next([=] {
const auto computed = computeThumbInfo();
if (!computed.image) {
if (_documentMedia && !_documentMedia->owner()->hasThumbnail()) {
_preview = QImage();
_lifetimeDownload.destroy();
}
return;
} else if (computed.loaded) {
_lifetimeDownload.destroy();
}
_preview = PreparePreviewImage(
computed.image->original(),
ImageRoundRadius::Large,
!!_spoiler);
}, _lifetimeDownload);
}
int MessagePreview::resizeGetHeight(int newWidth) {
return st::statisticsRecentPostRowHeight;
}
void MessagePreview::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto padding = st::boxRowPadding.left() / 2;
const auto rightWidth = std::max(_viewsWidth, _sharesWidth) + padding;
const auto left = _preview.isNull()
? 0
: (_preview.width() / style::DevicePixelRatio()) + padding;
if (left) {
p.drawImage(0, 0, _preview);
if (_spoiler) {
const auto rect = Rect(
_preview.size() / _preview.devicePixelRatio());
const auto paused = On(PowerSaving::kChatSpoiler);
FillSpoilerRect(
p,
rect,
Images::CornersMaskRef(
Images::CornersMask(st::roundRadiusLarge)),
Ui::DefaultImageSpoiler().frame(
_spoiler->index(crl::now(), paused)),
_cornerCache);
}
}
p.setBrush(Qt::NoBrush);
p.setPen(st::boxTextFg);
_text.draw(p, {
.position = { left, 0 },
.outerWidth = width() - left,
.availableWidth = width() - rightWidth - left,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = crl::now(),
.elisionHeight = st::statisticsDetailsPopupHeaderStyle.font->height,
});
_views.draw(p, {
.position = { width() - _viewsWidth, 0 },
.outerWidth = _viewsWidth,
.availableWidth = _viewsWidth,
});
p.setPen(st::windowSubTextFg);
_date.draw(p, {
.position = { left, height() / 2 },
.outerWidth = width() - left,
.availableWidth = width() - rightWidth - left,
});
_shares.draw(p, {
.position = { width() - _sharesWidth, height() / 2 },
.outerWidth = _sharesWidth,
.availableWidth = _sharesWidth,
});
}
} // namespace Info::Statistics

View File

@ -0,0 +1,61 @@
/*
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 "ui/rp_widget.h"
class HistoryItem;
namespace Data {
class DocumentMedia;
class PhotoMedia;
} // namespace Data
namespace Ui {
class SpoilerAnimation;
} // namespace Ui
namespace Info::Statistics {
class MessagePreview final : public Ui::RpWidget {
public:
MessagePreview(
not_null<Ui::RpWidget*> parent,
not_null<HistoryItem*> item,
int views,
int shares);
protected:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
private:
void processPreview(not_null<HistoryItem*> item);
not_null<HistoryItem*> _item;
Ui::Text::String _text;
Ui::Text::String _date;
Ui::Text::String _views;
Ui::Text::String _shares;
int _viewsWidth = 0;
int _sharesWidth = 0;
QImage _cornerCache;
QImage _preview;
std::shared_ptr<Data::PhotoMedia> _photoMedia;
std::shared_ptr<Data::DocumentMedia> _documentMedia;
std::unique_ptr<Ui::SpoilerAnimation> _spoiler;
rpl::lifetime _lifetimeDownload;
};
} // namespace Info::Statistics

View File

@ -8,9 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/statistics/info_statistics_widget.h"
#include "api/api_statistics.h"
#include "apiwrap.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "info/statistics/info_statistics_recent_message.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
@ -251,6 +254,31 @@ void FillLoading(
::Settings::AddSkip(content, st::settingsBlockedListIconPadding.top());
}
void AddHeader(
not_null<Ui::VerticalLayout*> content,
tr::phrase<> text,
const AnyStats &stats) {
const auto startDate = stats.channel
? stats.channel.startDate
: stats.supergroup.startDate;
const auto endDate = stats.channel
? stats.channel.endDate
: stats.supergroup.endDate;
const auto header = content->add(
object_ptr<Statistic::Header>(content),
st::statisticsLayerMargins + st::statisticsChartHeaderPadding);
header->resizeToWidth(header->width());
header->setTitle(text(tr::now));
const auto formatter = u"d MMM yyyy"_q;
const auto from = QDateTime::fromSecsSinceEpoch(startDate);
const auto to = QDateTime::fromSecsSinceEpoch(endDate);
header->setSubTitle(QLocale().toString(from.date(), formatter)
+ ' '
+ QChar(8212)
+ ' '
+ QLocale().toString(to.date(), formatter));
}
void FillOverview(
not_null<Ui::VerticalLayout*> content,
const AnyStats &stats) {
@ -258,25 +286,9 @@ void FillOverview(
const auto &channel = stats.channel;
const auto &supergroup = stats.supergroup;
const auto startDate = channel ? channel.startDate : supergroup.startDate;
const auto endDate = channel ? channel.endDate : supergroup.endDate;
::Settings::AddSkip(content, st::statisticsLayerOverviewMargins.top());
{
const auto header = content->add(
object_ptr<Statistic::Header>(content),
st::statisticsLayerMargins + st::statisticsChartHeaderPadding);
header->resizeToWidth(header->width());
header->setTitle(tr::lng_stats_overview_title(tr::now));
const auto formatter = u"d MMM yyyy"_q;
const auto from = QDateTime::fromSecsSinceEpoch(startDate);
const auto to = QDateTime::fromSecsSinceEpoch(endDate);
header->setSubTitle(QLocale().toString(from.date(), formatter)
+ ' '
+ QChar(8212)
+ ' '
+ QLocale().toString(to.date(), formatter));
}
AddHeader(content, tr::lng_stats_overview_title, stats);
::Settings::AddSkip(content);
struct Second final {
@ -421,6 +433,54 @@ void FillOverview(
::Settings::AddSkip(content, st::statisticsLayerOverviewMargins.bottom());
}
void FillRecentPosts(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
const Data::ChannelStatistics &stats) {
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
wrap->toggle(false, anim::type::instant);
const auto content = wrap->entity();
AddHeader(content, tr::lng_stats_recent_messages_title, { stats, {} });
::Settings::AddSkip(content);
const auto addMessage = [=](
not_null<Ui::VerticalLayout*> messageWrap,
not_null<HistoryItem*> item,
const Data::StatisticsMessageInteractionInfo &info) {
const auto row = messageWrap->add(
object_ptr<MessagePreview>(
messageWrap,
item,
info.viewsCount,
info.forwardsCount),
st::boxRowPadding);
::Settings::AddSkip(messageWrap);
content->resizeToWidth(content->width());
if (!wrap->toggled()) {
wrap->toggle(true, anim::type::normal);
}
};
for (const auto &recent : stats.recentMessageInteractions) {
const auto messageWrap = content->add(
object_ptr<Ui::VerticalLayout>(content));
const auto msgId = recent.messageId;
if (const auto item = peer->owner().message(peer, msgId)) {
addMessage(messageWrap, item, recent);
continue;
}
const auto callback = [=] {
if (const auto item = peer->owner().message(peer, msgId)) {
addMessage(messageWrap, item, recent);
}
};
peer->session().api().requestMessageData(peer, msgId, callback);
}
}
} // namespace
Memento::Memento(not_null<Controller*> controller)
@ -481,6 +541,9 @@ Widget::Widget(
}
FillOverview(inner, anyStats);
FillStatistic(inner, descriptor, anyStats);
if (anyStats.channel) {
FillRecentPosts(inner, descriptor.peer, anyStats.channel);
}
loaded->fire(true);
inner->resizeToWidth(width());
inner->showChildren();

View File

@ -98,3 +98,5 @@ statisticsOverviewSubtext: FlatLabel(boxLabel) {
}
statisticsOverviewMidSkip: 50px;
statisticsOverviewRightSkip: 14px;
statisticsRecentPostRowHeight: 40px;