mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 22:27:20 +02:00
Added initial support of recent posts to statistical info.
This commit is contained in:
parent
fc3acff5d6
commit
8564e4d727
6 changed files with 378 additions and 17 deletions
|
@ -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
|
||||
|
|
|
@ -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.";
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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();
|
||||
|
|
|
@ -98,3 +98,5 @@ statisticsOverviewSubtext: FlatLabel(boxLabel) {
|
|||
}
|
||||
statisticsOverviewMidSkip: 50px;
|
||||
statisticsOverviewRightSkip: 14px;
|
||||
|
||||
statisticsRecentPostRowHeight: 40px;
|
||||
|
|
Loading…
Add table
Reference in a new issue