From 98c6a3ff79eaa198837674397653e7d84dfa0b1e Mon Sep 17 00:00:00 2001
From: 23rd <23rd@vivaldi.net>
Date: Wed, 6 Dec 2023 21:27:33 +0300
Subject: [PATCH] Added support of stories in list of public forwards in
 statistics info.

---
 Telegram/SourceFiles/api/api_statistics.cpp   |  23 ++--
 .../info_statistics_inner_widget.cpp          |   7 +-
 .../statistics/info_statistics_inner_widget.h |   1 +
 .../info_statistics_list_controllers.cpp      | 115 +++++++++++++-----
 .../info_statistics_list_controllers.h        |   2 +-
 .../statistics/info_statistics_widget.cpp     |  12 +-
 6 files changed, 115 insertions(+), 45 deletions(-)

diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp
index 6655c618e..e889c3cdb 100644
--- a/Telegram/SourceFiles/api/api_statistics.cpp
+++ b/Telegram/SourceFiles/api/api_statistics.cpp
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "apiwrap.h"
 #include "data/data_channel.h"
 #include "data/data_session.h"
+#include "data/data_stories.h"
+#include "data/data_story.h"
 #include "history/history.h"
 #include "main/main_session.h"
 #include "statistics/statistics_data_deserialize.h"
@@ -475,9 +477,10 @@ void PublicForwards::requestStory(
 		_requestId = 0;
 
 		const auto &data = tlForwards.data();
+		auto &owner = channel->owner();
 
-		channel->owner().processUsers(data.vusers());
-		channel->owner().processChats(data.vchats());
+		owner.processUsers(data.vusers());
+		owner.processChats(data.vchats());
 
 		const auto nextToken = Data::PublicForwardsSlice::OffsetToken({
 			.storyOffset = data.vnext_offset().value_or_empty(),
@@ -494,23 +497,23 @@ void PublicForwards::requestStory(
 				const auto msgId = IdFromMessage(message);
 				const auto peerId = PeerFromMessage(message);
 				const auto lastDate = DateFromMessage(message);
-				if (const auto peer = channel->owner().peerLoaded(peerId)) {
+				if (const auto peer = owner.peerLoaded(peerId)) {
 					if (!lastDate) {
 						return;
 					}
-					channel->owner().addNewMessage(
+					owner.addNewMessage(
 						message,
 						MessageFlags(),
 						NewMessageType::Existing);
 					recentList.push_back({ .messageId = { peerId, msgId } });
 				}
 			}, [&](const MTPDpublicForwardStory &data) {
-				data.vstory().match([&](const MTPDstoryItem &d) {
-					recentList.push_back({
-						.storyId = { peerFromMTP(data.vpeer()), d.vid().v }
-					});
-				}, [](const auto &) {
-				});
+				const auto story = owner.stories().applyFromWebpage(
+					peerFromMTP(data.vpeer()),
+					data.vstory());
+				if (story) {
+					recentList.push_back({ .storyId = story->fullId() });
+				}
 			});
 		}
 
diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp
index ca77f7ad5..20814f3ea 100644
--- a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp
+++ b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.cpp
@@ -728,7 +728,12 @@ void InnerWidget::fill() {
 		AddPublicForwards(
 			_state.publicForwardsFirstSlice,
 			inner,
-			[=](FullMsgId id) { _showRequests.fire({ .history = id }); },
+			[=](RecentPostId id) {
+				_showRequests.fire({
+					.history = id.messageId,
+					.story = id.storyId,
+				});
+			},
 			descriptor.peer,
 			RecentPostId{ .messageId = _contextId, .storyId = _storyId });
 	}
diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.h b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.h
index ed4e93a9f..ada52f65c 100644
--- a/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.h
+++ b/Telegram/SourceFiles/info/statistics/info_statistics_inner_widget.h
@@ -33,6 +33,7 @@ public:
 		FullMsgId history;
 		FullMsgId messageStatistic;
 		FullStoryId storyStatistic;
+		FullStoryId story;
 	};
 
 	InnerWidget(
diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
index cdaffc183..acead54d5 100644
--- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
+++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.cpp
@@ -9,22 +9,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 #include "api/api_statistics.h"
 #include "boxes/peer_list_controllers.h"
-#include "data/data_boosts.h"
 #include "data/data_channel.h"
 #include "data/data_session.h"
+#include "data/data_stories.h"
 #include "data/data_user.h"
 #include "history/history_item.h"
 #include "info/boosts/giveaway/boost_badge.h"
 #include "lang/lang_keys.h"
 #include "main/main_session.h"
+#include "ui/effects/outline_segments.h" // Ui::UnreadStoryOutlineGradient.
 #include "ui/effects/toggle_arrow.h"
-#include "ui/empty_userpic.h"
 #include "ui/painter.h"
 #include "ui/rect.h"
 #include "ui/vertical_list.h"
 #include "ui/widgets/buttons.h"
 #include "ui/wrap/slide_wrap.h"
 #include "ui/wrap/vertical_layout.h"
+#include "styles/style_dialogs.h" // dialogsStoriesFull.
 #include "styles/style_settings.h"
 #include "styles/style_statistics.h"
 #include "styles/style_window.h"
@@ -93,7 +94,7 @@ void AddSubtitle(
 
 struct PublicForwardsDescriptor final {
 	Data::PublicForwardsSlice firstSlice;
-	Fn<void(FullMsgId)> showPeerHistory;
+	Fn<void(Data::RecentPostId)> requestShow;
 	not_null<PeerData*> peer;
 	Data::RecentPostId contextId;
 };
@@ -110,24 +111,62 @@ struct BoostsDescriptor final {
 	not_null<PeerData*> peer;
 };
 
-class PeerListRowWithMsgId : public PeerListRow {
+class PeerListRowWithFullId : public PeerListRow {
 public:
-	using PeerListRow::PeerListRow;
+	PeerListRowWithFullId(
+		not_null<PeerData*> peer,
+		Data::RecentPostId contextId);
 
-	void setMsgId(MsgId msgId);
-	[[nodiscard]] MsgId msgId() const;
+	[[nodiscard]] PaintRoundImageCallback generatePaintUserpicCallback(
+		bool) override;
+
+	[[nodiscard]] Data::RecentPostId contextId() const;
 
 private:
-	MsgId _msgId;
+	const Data::RecentPostId _contextId;
 
 };
 
-void PeerListRowWithMsgId::setMsgId(MsgId msgId) {
-	_msgId = msgId;
+PeerListRowWithFullId::PeerListRowWithFullId(
+	not_null<PeerData*> peer,
+	Data::RecentPostId contextId)
+: PeerListRow(peer)
+, _contextId(contextId) {
 }
 
-MsgId PeerListRowWithMsgId::msgId() const {
-	return _msgId;
+PaintRoundImageCallback PeerListRowWithFullId::generatePaintUserpicCallback(
+		bool forceRound) {
+	if (!_contextId.storyId) {
+		return PeerListRow::generatePaintUserpicCallback(forceRound);
+	}
+	const auto peer = PeerListRow::peer();
+	auto userpic = PeerListRow::ensureUserpicView();
+
+	const auto line = st::dialogsStoriesFull.lineTwice;
+	const auto penWidth = line / 2.;
+	const auto offset = 1.5 * penWidth * 2;
+	return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
+		const auto rect = QRect(QPoint(x, y), Size(size));
+		peer->paintUserpicLeft(
+			p,
+			userpic,
+			x + offset,
+			y + offset,
+			outerWidth,
+			size - offset * 2);
+		auto hq = PainterHighQualityEnabler(p);
+		auto gradient = Ui::UnreadStoryOutlineGradient();
+		gradient.setStart(rect.topRight());
+		gradient.setFinalStop(rect.bottomLeft());
+
+		p.setPen(QPen(gradient, penWidth));
+		p.setBrush(Qt::NoBrush);
+		p.drawEllipse(rect - Margins(penWidth));
+	};
+}
+
+Data::RecentPostId PeerListRowWithFullId::contextId() const {
+	return _contextId;
 }
 
 class MembersController final : public PeerListController {
@@ -237,11 +276,11 @@ public:
 	void loadMoreRows() override;
 
 private:
-	void appendRow(not_null<PeerData*> peer, MsgId msgId);
+	void appendRow(not_null<PeerData*> peer, Data::RecentPostId contextId);
 	void applySlice(const Data::PublicForwardsSlice &slice);
 
 	const not_null<Main::Session*> _session;
-	Fn<void(FullMsgId)> _showPeerHistory;
+	Fn<void(Data::RecentPostId)> _requestShow;
 
 	Api::PublicForwards _api;
 	Data::PublicForwardsSlice _firstSlice;
@@ -253,7 +292,7 @@ private:
 
 PublicForwardsController::PublicForwardsController(PublicForwardsDescriptor d)
 : _session(&d.peer->session())
-, _showPeerHistory(std::move(d.showPeerHistory))
+, _requestShow(std::move(d.requestShow))
 , _api(d.peer->asChannel(), d.contextId)
 , _firstSlice(std::move(d.firstSlice)) {
 }
@@ -282,10 +321,13 @@ void PublicForwardsController::applySlice(
 	_apiToken = slice.token;
 
 	for (const auto &item : slice.list) {
-		// TODO support stories.
-		if (const auto fullId = item.messageId) {
-			if (const auto peer = session().data().peerLoaded(fullId.peer)) {
-				appendRow(peer, fullId.msg);
+		if (const auto &full = item.messageId) {
+			if (const auto peer = session().data().peerLoaded(full.peer)) {
+				appendRow(peer, item);
+			}
+		} else if (const auto &full = item.storyId) {
+			if (const auto story = session().data().stories().lookup(full)) {
+				appendRow((*story)->peer(), item);
 			}
 		}
 	}
@@ -293,25 +335,32 @@ void PublicForwardsController::applySlice(
 }
 
 void PublicForwardsController::rowClicked(not_null<PeerListRow*> row) {
-	const auto rowWithMsgId = static_cast<PeerListRowWithMsgId*>(row.get());
-	crl::on_main([=, msgId = rowWithMsgId->msgId(), peer = row->peer()] {
-		_showPeerHistory({ peer->id, msgId });
-	});
+	const auto rowWithId = static_cast<PeerListRowWithFullId*>(row.get());
+	crl::on_main([=, id = rowWithId->contextId()] { _requestShow(id); });
 }
 
 void PublicForwardsController::appendRow(
 		not_null<PeerData*> peer,
-		MsgId msgId) {
+		Data::RecentPostId contextId) {
 	if (delegate()->peerListFindRow(peer->id.value)) {
 		return;
 	}
 
-	auto row = std::make_unique<PeerListRowWithMsgId>(peer);
-	row->setMsgId(msgId);
+	auto row = std::make_unique<PeerListRowWithFullId>(peer, contextId);
 
-	const auto members = peer->asChannel()->membersCount();
-	const auto message = peer->owner().message({ peer->id, msgId });
-	const auto views = message ? message->viewsCount() : 0;
+	const auto members = peer->isChannel()
+		? peer->asChannel()->membersCount()
+		: 0;
+	const auto views = [&] {
+		if (contextId.messageId) {
+			const auto message = peer->owner().message(contextId.messageId);
+			return message ? message->viewsCount() : 0;
+		} else if (const auto &id = contextId.storyId) {
+			const auto story = peer->owner().stories().lookup(id);
+			return story ? (*story)->views() : 0;
+		}
+		return 0;
+	}();
 
 	const auto membersText = !members
 		? QString()
@@ -321,7 +370,9 @@ void PublicForwardsController::appendRow(
 	const auto viewsText = views
 		? tr::lng_stats_recent_messages_views({}, lt_count_decimal, views)
 		: QString();
-	const auto resultText = (membersText.isEmpty() || viewsText.isEmpty())
+	const auto resultText = (membersText.isEmpty() && viewsText.isEmpty())
+		? tr::lng_stories_no_views(tr::now)
+		: (membersText.isEmpty() || viewsText.isEmpty())
 		? membersText + viewsText
 		: QString("%1, %2").arg(membersText, viewsText);
 	row->setCustomStatus(resultText);
@@ -618,7 +669,7 @@ rpl::producer<int> BoostsController::totalBoostsValue() const {
 void AddPublicForwards(
 		const Data::PublicForwardsSlice &firstSlice,
 		not_null<Ui::VerticalLayout*> container,
-		Fn<void(FullMsgId)> showPeerHistory,
+		Fn<void(Data::RecentPostId)> requestShow,
 		not_null<PeerData*> peer,
 		Data::RecentPostId contextId) {
 	if (!peer->isChannel()) {
@@ -633,7 +684,7 @@ void AddPublicForwards(
 	};
 	auto d = PublicForwardsDescriptor{
 		firstSlice,
-		std::move(showPeerHistory),
+		std::move(requestShow),
 		peer,
 		contextId,
 	};
diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h
index 0696062c7..2a2dfe93a 100644
--- a/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h
+++ b/Telegram/SourceFiles/info/statistics/info_statistics_list_controllers.h
@@ -26,7 +26,7 @@ namespace Info::Statistics {
 void AddPublicForwards(
 	const Data::PublicForwardsSlice &firstSlice,
 	not_null<Ui::VerticalLayout*> container,
-	Fn<void(FullMsgId)> showPeerHistory,
+	Fn<void(Data::RecentPostId)> requestShow,
 	not_null<PeerData*> peer,
 	Data::RecentPostId contextId);
 
diff --git a/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp b/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp
index 3544b2ae0..6c978b260 100644
--- a/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp
+++ b/Telegram/SourceFiles/info/statistics/info_statistics_widget.cpp
@@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "info/statistics/info_statistics_widget.h"
 
-#include "info/statistics/info_statistics_inner_widget.h"
+#include "data/data_session.h"
+#include "data/data_stories.h"
 #include "info/info_controller.h"
 #include "info/info_memento.h"
+#include "info/statistics/info_statistics_inner_widget.h"
 #include "lang/lang_keys.h"
+#include "main/main_session.h"
 
 namespace Info::Statistics {
 
@@ -78,6 +81,13 @@ Widget::Widget(
 				controller->statisticsPeer(),
 				request.messageStatistic,
 				request.storyStatistic));
+		} else if (const auto &s = request.story) {
+			if (const auto peer = controller->session().data().peer(s.peer)) {
+				controller->parentController()->openPeerStory(
+					peer,
+					s.story,
+					{ Data::StoriesContextSingle() });
+			}
 		}
 	}, _inner->lifetime());
 	_inner->scrollToRequests(