From a9bd7803e6a25138c5b7ee265ce881bd8bd6ce27 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 18 Jun 2024 18:55:07 +0400
Subject: [PATCH] Edit price on send, send single paid media.

---
 Telegram/Resources/langs/lang.strings         |   9 +
 Telegram/SourceFiles/api/api_common.h         |   1 +
 Telegram/SourceFiles/api/api_views.cpp        |  29 ++-
 Telegram/SourceFiles/api/api_views.h          |   3 +-
 Telegram/SourceFiles/apiwrap.cpp              |   6 +-
 .../SourceFiles/boxes/edit_caption_box.cpp    |   1 +
 Telegram/SourceFiles/boxes/send_files_box.cpp | 168 +++++++++++++++++-
 Telegram/SourceFiles/boxes/send_files_box.h   |  10 +-
 .../chat_helpers/chat_helpers.style           |  14 ++
 Telegram/SourceFiles/data/data_cloud_file.cpp |   6 +
 .../SourceFiles/data/data_media_types.cpp     |   4 +-
 Telegram/SourceFiles/data/data_media_types.h  |   1 +
 .../view/media/history_view_media_common.cpp  |  41 +++++
 .../view/media/history_view_media_common.h    |   2 +
 .../history/view/media/history_view_photo.cpp |  61 ++++++-
 .../history/view/media/history_view_photo.h   |   3 +
 .../SourceFiles/media/view/media_view.style   |   1 +
 Telegram/SourceFiles/menu/menu_send.cpp       |   8 +
 Telegram/SourceFiles/menu/menu_send.h         |   2 +
 .../payments/payments_non_panel_process.cpp   |   3 -
 .../payments/payments_non_panel_process.h     |   2 +
 .../attach_abstract_single_media_preview.cpp  |   8 +-
 .../attach_abstract_single_media_preview.h    |   4 +-
 .../ui/chat/attach/attach_album_preview.cpp   |   6 +-
 .../ui/chat/attach/attach_album_preview.h     |   4 +-
 .../attach_item_single_media_preview.cpp      |   2 +-
 .../ui/chat/attach/attach_prepare.cpp         |   4 +
 .../ui/chat/attach/attach_prepare.h           |   3 +
 .../attach/attach_single_media_preview.cpp    |   9 +-
 .../chat/attach/attach_single_media_preview.h |   4 +-
 30 files changed, 382 insertions(+), 37 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 0043c59b3..f4d2c145d 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -3311,6 +3311,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 "lng_context_spoiler_effect" = "Hide with Spoiler";
 "lng_context_disable_spoiler" = "Remove Spoiler";
+"lng_context_make_paid" = "Make This Content Paid";
+"lng_context_change_price" = "Change Price";
 
 "lng_factcheck_title" = "Fact Check";
 "lng_factcheck_placeholder" = "Add Facts or Context";
@@ -3322,6 +3324,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
 "lng_factcheck_links" = "Only **t.me/** links are allowed.";
 
+"lng_paid_title" = "Paid Content";
+"lng_paid_enter_cost" = "Enter Unlock Cost";
+"lng_paid_cost_placeholder" = "Stars to Unlock";
+"lng_paid_about" = "Users will have to transfer this amount of Stars to your channel in order to view this media. {link}";
+"lng_paid_about_link" = "More about stars >";
+"lng_paid_price" = "Unlock for {price}";
+
 "lng_translate_show_original" = "Show Original";
 "lng_translate_bar_to" = "Translate to {name}";
 "lng_translate_bar_to_other" = "Translate to {name}";
diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h
index efd92a9bc..cd8aa54e2 100644
--- a/Telegram/SourceFiles/api/api_common.h
+++ b/Telegram/SourceFiles/api/api_common.h
@@ -20,6 +20,7 @@ namespace Api {
 inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
 
 struct SendOptions {
+	uint64 price = 0;
 	PeerData *sendAs = nullptr;
 	TimeId scheduled = 0;
 	BusinessShortcutId shortcutId = 0;
diff --git a/Telegram/SourceFiles/api/api_views.cpp b/Telegram/SourceFiles/api/api_views.cpp
index 6c8ec8df7..e80f56e21 100644
--- a/Telegram/SourceFiles/api/api_views.cpp
+++ b/Telegram/SourceFiles/api/api_views.cpp
@@ -55,7 +55,9 @@ void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
 	_incremented.remove(peer);
 }
 
-void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
+void ViewsManager::pollExtendedMedia(
+		not_null<HistoryItem*> item,
+		bool force) {
 	if (!item->isRegular()) {
 		return;
 	}
@@ -63,14 +65,20 @@ void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
 	const auto peer = item->history()->peer;
 	auto &request = _pollRequests[peer];
 	if (request.ids.contains(id) || request.sent.contains(id)) {
-		return;
+		if (!force || request.forced) {
+			return;
+		}
 	}
 	request.ids.emplace(id);
-	if (!request.id && !request.when) {
-		request.when = crl::now() + kPollExtendedMediaPeriod;
+	if (force) {
+		request.forced = true;
 	}
-	if (!_pollTimer.isActive()) {
-		_pollTimer.callOnce(kPollExtendedMediaPeriod);
+	const auto delay = force ? 1 : kPollExtendedMediaPeriod;
+	if (!request.id && (!request.when || force)) {
+		request.when = crl::now() + delay;
+	}
+	if (!_pollTimer.isActive() || force) {
+		_pollTimer.callOnce(delay);
 	}
 }
 
@@ -160,9 +168,12 @@ void ViewsManager::sendPollRequests(
 					if (i->second.ids.empty()) {
 						i = _pollRequests.erase(i);
 					} else {
-						i->second.when = now + kPollExtendedMediaPeriod;
-						if (!_pollTimer.isActive()) {
-							_pollTimer.callOnce(kPollExtendedMediaPeriod);
+						const auto delay = i->second.forced
+							? 1
+							: kPollExtendedMediaPeriod;
+						i->second.when = now + delay;
+						if (!_pollTimer.isActive() || i->second.forced) {
+							_pollTimer.callOnce(delay);
 						}
 						++i;
 					}
diff --git a/Telegram/SourceFiles/api/api_views.h b/Telegram/SourceFiles/api/api_views.h
index 759d3c6a8..f8d19d15b 100644
--- a/Telegram/SourceFiles/api/api_views.h
+++ b/Telegram/SourceFiles/api/api_views.h
@@ -26,7 +26,7 @@ public:
 	void scheduleIncrement(not_null<HistoryItem*> item);
 	void removeIncremented(not_null<PeerData*> peer);
 
-	void pollExtendedMedia(not_null<HistoryItem*> item);
+	void pollExtendedMedia(not_null<HistoryItem*> item, bool force = false);
 
 private:
 	struct PollExtendedMediaRequest {
@@ -34,6 +34,7 @@ private:
 		mtpRequestId id = 0;
 		base::flat_set<MsgId> ids;
 		base::flat_set<MsgId> sent;
+		bool forced = false;
 	};
 
 	void viewsIncrement();
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 62f2cfbd4..424c8ccc1 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -4188,7 +4188,11 @@ void ApiWrap::sendMediaWithRandomId(
 			MTP_flags(flags),
 			peer->input,
 			Data::Histories::ReplyToPlaceholder(),
-			media,
+			(options.price
+				? MTPInputMedia(MTP_inputMediaPaidMedia(
+					MTP_long(options.price),
+					MTP_vector<MTPInputMedia>(1, media)))
+				: media),
 			MTP_string(caption.text),
 			MTP_long(randomId),
 			MTPReplyMarkup(),
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index d46121c2f..bd089a9b5 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -463,6 +463,7 @@ void EditCaptionBox::rebuildPreview() {
 			st::defaultComposeControls,
 			gifPaused,
 			file,
+			[] { return true; },
 			Ui::AttachControls::Type::EditOnly);
 		_isPhoto = (media && media->isPhoto());
 		const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp
index fbaaf561a..f80279345 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_files_box.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "lang/lang_keys.h"
 #include "storage/localstorage.h"
 #include "storage/storage_media_prepare.h"
+#include "iv/iv_instance.h"
 #include "mainwidget.h"
 #include "main/main_session.h"
 #include "main/main_session_settings.h"
@@ -36,9 +37,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/chat/attach/attach_single_file_preview.h"
 #include "ui/chat/attach/attach_single_media_preview.h"
 #include "ui/grouped_layout.h"
+#include "ui/text/text_utilities.h"
 #include "ui/toast/toast.h"
 #include "ui/controls/emoji_button.h"
 #include "ui/painter.h"
+#include "ui/vertical_list.h"
 #include "lottie/lottie_single_player.h"
 #include "data/data_document.h"
 #include "data/data_user.h"
@@ -58,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace {
 
 constexpr auto kMaxMessageLength = 4096;
+constexpr auto kMaxPrice = 1000ULL;
 
 using Ui::SendFilesWay;
 
@@ -103,6 +107,74 @@ rpl::producer<QString> FieldPlaceholder(
 		: tr::lng_photos_comment();
 }
 
+void EditPriceBox(
+		not_null<Ui::GenericBox*> box,
+		not_null<Main::Session*> session,
+		uint64 price,
+		Fn<void(uint64)> apply) {
+	const auto owner = &session->data();
+	box->setTitle(tr::lng_paid_title());
+	AddSubsectionTitle(
+		box->verticalLayout(),
+		tr::lng_paid_enter_cost(),
+		(st::boxRowPadding - QMargins(
+			st::defaultSubsectionTitlePadding.left(),
+			0,
+			st::defaultSubsectionTitlePadding.right(),
+			0)));
+	const auto field = box->addRow(object_ptr<Ui::InputField>(
+		box,
+		st::editTagField,
+		tr::lng_paid_cost_placeholder(),
+		price ? QString::number(price) : QString()));
+	field->selectAll();
+	field->setMaxLength(QString::number(kMaxPrice).size());
+	box->setFocusCallback([=] {
+		field->setFocusFast();
+	});
+	const auto about = box->addRow(
+		object_ptr<Ui::FlatLabel>(
+			box,
+			tr::lng_paid_about(
+				lt_link,
+				tr::lng_paid_about_link() | Ui::Text::ToLink(),
+				Ui::Text::WithEntities),
+			st::paidAmountAbout),
+		st::boxRowPadding + QMargins(0, st::sendMediaRowSkip, 0, 0));
+	about->setClickHandlerFilter([=](const auto &...) {
+		Core::App().iv().openWithIvPreferred(
+			session,
+			u"https://telegram.org/blog/telegram-stars"_q);
+		return false;
+	});
+
+	field->paintRequest() | rpl::start_with_next([=](QRect clip) {
+		auto p = QPainter(field);
+		st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
+	}, field->lifetime());
+
+	const auto save = [=] {
+		const auto now = field->getLastText().toULongLong();
+		if (now > kMaxPrice) {
+			field->showError();
+			return;
+		}
+		const auto weak = Ui::MakeWeak(box);
+		apply(now);
+		if (const auto strong = weak.data()) {
+			strong->closeBox();
+		}
+	};
+
+	field->submits(
+	) | rpl::start_with_next(save, field->lifetime());
+
+	box->addButton(tr::lng_settings_save(), save);
+	box->addButton(tr::lng_cancel(), [=] {
+		box->closeBox();
+	});
+}
+
 } // namespace
 
 SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer) {
@@ -153,7 +225,8 @@ SendFilesBox::Block::Block(
 	int from,
 	int till,
 	Fn<bool()> gifPaused,
-	SendFilesWay way)
+	SendFilesWay way,
+	Fn<bool()> canToggleSpoiler)
 : _items(items)
 , _from(from)
 , _till(till) {
@@ -170,14 +243,16 @@ SendFilesBox::Block::Block(
 			parent.get(),
 			st,
 			my,
-			way);
+			way,
+			std::move(canToggleSpoiler));
 		_preview.reset(preview);
 	} else {
 		const auto media = Ui::SingleMediaPreview::Create(
 			parent,
 			st,
 			gifPaused,
-			first);
+			first,
+			std::move(canToggleSpoiler));
 		if (media) {
 			_isSingleMedia = true;
 			_preview.reset(media);
@@ -385,6 +460,9 @@ Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
 			: _invertCaption
 			? SendMenu::CaptionState::Above
 			: SendMenu::CaptionState::Below;
+		result.price = canChangePrice()
+			? _price.current()
+			: std::optional<uint64>();
 		return result;
 	});
 }
@@ -398,6 +476,7 @@ auto SendFilesBox::prepareSendMenuCallback()
 		case Type::CaptionUp: _invertCaption = true; break;
 		case Type::SpoilerOn: toggleSpoilers(true); break;
 		case Type::SpoilerOff: toggleSpoilers(false); break;
+		case Type::ChangePrice: changePrice(); break;
 		default:
 			SendMenu::DefaultCallback(
 				_show,
@@ -588,14 +667,22 @@ void SendFilesBox::refreshButtons() {
 	addMenuButton();
 }
 
-bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
+bool SendFilesBox::hasSendMenu(const MenuDetails &details) const {
 	return (details.type != SendMenu::Type::Disabled)
 		|| (details.spoiler != SendMenu::SpoilerState::None)
 		|| (details.caption != SendMenu::CaptionState::None);
 }
 
 bool SendFilesBox::hasSpoilerMenu() const {
-	return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
+	return !hasPrice()
+		&& _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
+}
+
+bool SendFilesBox::canChangePrice() const {
+	const auto way = _sendWay.current();
+	return _list.canChangePrice(
+		way.groupFiles() && way.sendImagesAsPhotos(),
+		way.sendImagesAsPhotos());
 }
 
 void SendFilesBox::applyBlockChanges() {
@@ -618,6 +705,71 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
 	}
 }
 
+void SendFilesBox::changePrice() {
+	const auto weak = Ui::MakeWeak(this);
+	const auto session = &_show->session();
+	const auto now = _price.current();
+	_show->show(Box(EditPriceBox, session, now, [=](uint64 price) {
+		if (weak && price != now) {
+			_price = price;
+			refreshPriceTag();
+		}
+	}));
+}
+
+bool SendFilesBox::hasPrice() const {
+	return canChangePrice() && _price.current() > 0;
+}
+
+void SendFilesBox::refreshPriceTag() {
+	const auto resetSpoilers = hasPrice() || _priceTag;
+	if (resetSpoilers) {
+		for (auto &file : _list.files) {
+			file.spoiler = false;
+		}
+		for (auto &block : _blocks) {
+			block.toggleSpoilers(hasPrice());
+		}
+	}
+	if (!hasPrice()) {
+		_priceTag = nullptr;
+	} else if (!_priceTag) {
+		_priceTag = std::make_unique<Ui::RpWidget>(_inner.data());
+		const auto raw = _priceTag.get();
+
+		raw->show();
+		raw->paintRequest() | rpl::start_with_next([=] {
+			auto p = QPainter(raw);
+			auto hq = PainterHighQualityEnabler(p);
+			p.setBrush(st::toastBg);
+			p.setPen(Qt::NoPen);
+			const auto radius = std::min(raw->width(), raw->height()) / 2.;
+			p.drawRoundedRect(raw->rect(), radius, radius);
+		}, raw->lifetime());
+
+		auto price = _price.value() | rpl::map([=](uint64 amount) {
+			return QChar(0x2B50) + Lang::FormatCountDecimal(amount);
+		});
+		const auto label = Ui::CreateChild<Ui::FlatLabel>(
+			raw,
+			tr::lng_paid_price(lt_price, std::move(price)),
+			st::paidTagLabel);
+		label->sizeValue() | rpl::start_with_next([=](QSize size) {
+			const auto inner = QRect(QPoint(), size);
+			const auto rect = inner.marginsAdded(st::paidTagPadding);
+			raw->resize(rect.size());
+			label->move(-rect.topLeft());
+		}, label->lifetime());
+		_inner->sizeValue() | rpl::start_with_next([=](QSize size) {
+			raw->move(
+				(size.width() - raw->width()) / 2,
+				(size.height() - raw->height()) / 2);
+		}, raw->lifetime());
+	} else {
+		_priceTag->raise();
+	}
+}
+
 void SendFilesBox::addMenuButton() {
 	const auto details = _sendMenuDetails();
 	if (!hasSendMenu(details)) {
@@ -766,7 +918,8 @@ void SendFilesBox::pushBlock(int from, int till) {
 		from,
 		till,
 		gifPaused,
-		_sendWay.current());
+		_sendWay.current(),
+		[=] { return !hasPrice(); });
 	auto &block = _blocks.back();
 	const auto widget = _inner->add(
 		block.takeWidget(),
@@ -893,6 +1046,7 @@ void SendFilesBox::pushBlock(int from, int till) {
 
 void SendFilesBox::refreshControls(bool initial) {
 	refreshButtons();
+	refreshPriceTag();
 	refreshTitleText();
 	updateSendWayControls();
 	updateCaptionPlaceholder();
@@ -1447,6 +1601,7 @@ void SendFilesBox::send(
 		auto child = _sendMenuDetails();
 		child.spoiler = SendMenu::SpoilerState::None;
 		child.caption = SendMenu::CaptionState::None;
+		child.price = std::nullopt;
 		return SendMenu::DefaultCallback(_show, sendCallback())(
 			{ .type = SendMenu::ActionType::Schedule },
 			child);
@@ -1475,6 +1630,7 @@ void SendFilesBox::send(
 			? _caption->getTextWithAppliedMarkdown()
 			: TextWithTags();
 		options.invertCaption = _invertCaption;
+		options.price = hasPrice() ? _price.current() : 0;
 		if (!validateLength(caption.text)) {
 			return;
 		}
diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h
index 1e95a1aa8..f16dffb8f 100644
--- a/Telegram/SourceFiles/boxes/send_files_box.h
+++ b/Telegram/SourceFiles/boxes/send_files_box.h
@@ -149,7 +149,8 @@ private:
 			int from,
 			int till,
 			Fn<bool()> gifPaused,
-			Ui::SendFilesWay way);
+			Ui::SendFilesWay way,
+			Fn<bool()> canToggleSpoiler);
 		Block(Block &&other) = default;
 		Block &operator=(Block &&other) = default;
 
@@ -190,6 +191,11 @@ private:
 	void addMenuButton();
 	void applyBlockChanges();
 	void toggleSpoilers(bool enabled);
+	void changePrice();
+
+	[[nodiscard]] bool canChangePrice() const;
+	[[nodiscard]] bool hasPrice() const;
+	void refreshPriceTag();
 
 	bool validateLength(const QString &text) const;
 	void refreshButtons();
@@ -251,6 +257,8 @@ private:
 	SendFilesCheck _check;
 	SendFilesConfirmed _confirmedCallback;
 	Fn<void()> _cancelledCallback;
+	rpl::variable<uint64> _price = 0;
+	std::unique_ptr<Ui::RpWidget> _priceTag;
 	bool _confirmed = false;
 	bool _invertCaption = false;
 
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index 6b69aa136..b268377f9 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -71,6 +71,7 @@ ComposeIcons {
 	menuSpoilerOff: icon;
 	menuBelow: icon;
 	menuAbove: icon;
+	menuPrice: icon;
 
 	stripBubble: icon;
 	stripExpandPanel: icon;
@@ -610,6 +611,7 @@ defaultComposeIcons: ComposeIcons {
 	menuSpoilerOff: menuIconSpoilerOff;
 	menuBelow: menuIconBelow;
 	menuAbove: menuIconAbove;
+	menuPrice: menuIconEarn;
 
 	stripBubble: icon{
 		{ "chat/reactions_bubble_shadow", windowShadowFg },
@@ -1406,3 +1408,15 @@ editTagField: InputField(defaultInputField) {
 editTagLimit: FlatLabel(defaultFlatLabel) {
 	textFg: windowSubTextFg;
 }
+
+paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
+paidStarIconTop: 7px;
+paidAmountAbout: FlatLabel(defaultFlatLabel) {
+	minWidth: 256px;
+	textFg: windowSubTextFg;
+}
+paidTagLabel: FlatLabel(defaultFlatLabel) {
+	textFg: radialFg;
+	style: semiboldTextStyle;
+}
+paidTagPadding: margins(16px, 6px, 16px, 6px);
diff --git a/Telegram/SourceFiles/data/data_cloud_file.cpp b/Telegram/SourceFiles/data/data_cloud_file.cpp
index 526dd8904..7b3e73828 100644
--- a/Telegram/SourceFiles/data/data_cloud_file.cpp
+++ b/Telegram/SourceFiles/data/data_cloud_file.cpp
@@ -170,6 +170,12 @@ void UpdateCloudFile(
 		if (data.progressivePartSize && !file.location.valid()) {
 			file.progressivePartSize = data.progressivePartSize;
 		}
+		if (data.location.width()
+			&& data.location.height()
+			&& !file.location.valid()
+			&& !file.location.width()) {
+			file.location = data.location;
+		}
 		return;
 	}
 
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index e076438d7..1a8b052db 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -323,7 +323,7 @@ bool UpdateExtendedMedia(
 	auto changed = false;
 	const auto count = int(media.size());
 	for (auto i = 0; i != count; ++i) {
-		if (i < invoice.extendedMedia.size()) {
+		if (i <= invoice.extendedMedia.size()) {
 			invoice.extendedMedia.emplace_back();
 			changed = true;
 		}
@@ -388,6 +388,7 @@ Invoice ComputeInvoiceData(
 	auto result = Invoice{
 		.amount = data.vstars_amount().v,
 		.currency = Ui::kCreditsCurrency,
+		.isPaidMedia = true,
 	};
 	UpdateExtendedMedia(result, item, data.vextended_media().v);
 	return result;
@@ -1908,6 +1909,7 @@ MediaInvoice::MediaInvoice(
 	.title = data.title,
 	.description = data.description,
 	.photo = data.photo,
+	.isPaidMedia = data.isPaidMedia,
 	.isTest = data.isTest,
 } {
 	_invoice.extendedMedia.reserve(data.extendedMedia.size());
diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h
index 3e26877d4..62d03c53d 100644
--- a/Telegram/SourceFiles/data/data_media_types.h
+++ b/Telegram/SourceFiles/data/data_media_types.h
@@ -94,6 +94,7 @@ struct Invoice {
 	TextWithEntities description;
 	std::vector<std::unique_ptr<Media>> extendedMedia;
 	PhotoData *photo = nullptr;
+	bool isPaidMedia = false;
 	bool isTest = false;
 };
 [[nodiscard]] bool HasExtendedMedia(const Invoice &invoice);
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
index 273262e8d..e5c01ba70 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp
@@ -7,9 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/view/media/history_view_media_common.h"
 
+#include "api/api_views.h"
+#include "apiwrap.h"
 #include "ui/text/format_values.h"
 #include "ui/painter.h"
+#include "core/click_handler_types.h"
 #include "data/data_document.h"
+#include "data/data_session.h"
 #include "data/data_wall_paper.h"
 #include "data/data_media_types.h"
 #include "history/view/history_view_element.h"
@@ -19,7 +23,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/media/history_view_document.h"
 #include "history/view/media/history_view_sticker.h"
 #include "history/view/media/history_view_theme_document.h"
+#include "history/history_item.h"
+#include "history/history.h"
+#include "main/main_session.h"
+#include "mainwindow.h"
 #include "media/streaming/media_streaming_utility.h"
+#include "payments/payments_checkout_process.h"
+#include "payments/payments_non_panel_process.h"
+#include "window/window_session_controller.h"
 #include "styles/style_chat.h"
 
 namespace HistoryView {
@@ -180,4 +191,34 @@ QSize CountPhotoMediaSize(
 			media.scaled(media.width(), newWidth, Qt::KeepAspectRatio));
 }
 
+ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item) {
+	return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
+		const auto my = context.other.value<ClickHandlerContext>();
+		const auto controller = my.sessionWindow.get();
+		const auto itemId = item->fullId();
+		const auto session = &item->history()->session();
+		using Result = Payments::CheckoutResult;
+		const auto done = crl::guard(session, [=](Result result) {
+			if (result != Result::Paid) {
+				return;
+			} else if (const auto item = session->data().message(itemId)) {
+				session->api().views().pollExtendedMedia(item, true);
+			}
+		});
+		Payments::CheckoutProcess::Start(
+			item,
+			Payments::Mode::Payment,
+			(controller
+				? crl::guard(
+					controller,
+					[=](auto) { controller->widget()->activate(); })
+				: Fn<void(Payments::CheckoutResult)>()),
+			((controller && Payments::IsCreditsInvoice(item))
+				? Payments::ProcessNonPanelPaymentFormFactory(
+					controller,
+					done)
+				: nullptr));
+	});
+}
+
 } // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.h b/Telegram/SourceFiles/history/view/media/history_view_media_common.h
index 2a46f5b3e..a37d0ab62 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_common.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.h
@@ -75,4 +75,6 @@ void PaintInterpolatedIcon(
 	int newWidth,
 	int maxWidth);
 
+[[nodiscard]] ClickHandlerPtr MakePaidMediaLink(not_null<HistoryItem*> item);
+
 } // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
index 01a41bdaa..7650fcd9a 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/view/history_view_cursor_state.h"
 #include "history/view/media/history_view_media_common.h"
 #include "history/view/media/history_view_media_spoiler.h"
+#include "lang/lang_keys.h"
 #include "media/streaming/media_streaming_instance.h"
 #include "media/streaming/media_streaming_player.h"
 #include "media/streaming/media_streaming_document.h"
@@ -38,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_web_page.h"
 #include "core/application.h"
 #include "styles/style_chat.h"
+#include "styles/style_chat_helpers.h"
 
 namespace HistoryView {
 namespace {
@@ -140,7 +142,8 @@ void Photo::dataMediaCreated() const {
 
 	if (_data->inlineThumbnailBytes().isEmpty()
 		&& !_dataMedia->image(PhotoSize::Large)
-		&& !_dataMedia->image(PhotoSize::Thumbnail)) {
+		&& !_dataMedia->image(PhotoSize::Thumbnail)
+		&& !_data->extendedMediaPreview()) {
 		_dataMedia->wanted(PhotoSize::Small, _realParent->fullId());
 	}
 	history()->owner().registerHeavyViewPart(_parent);
@@ -277,8 +280,9 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
 	_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
 	const auto st = context.st;
 	const auto sti = context.imageStyle();
-	auto loaded = _dataMedia->loaded();
-	auto displayLoading = _data->displayLoading();
+	const auto preview = _data->extendedMediaPreview();
+	auto loaded = preview || _dataMedia->loaded();
+	auto displayLoading = !preview && _data->displayLoading();
 
 	auto inWebPage = (_parent->media() != this);
 	auto paintx = 0, painty = 0, paintw = width(), painth = height();
@@ -365,6 +369,8 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
 			QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
 			_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
 		}
+	} else if (preview) {
+		paintPriceTag(p, rthumb);
 	}
 	if (showEnlarge) {
 		auto hq = PainterHighQualityEnabler(p);
@@ -397,6 +403,43 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
 	}
 }
 
+void Photo::paintPriceTag(Painter &p, QRect rthumb) const {
+	const auto media = parent()->data()->media();
+	const auto invoice = media ? media->invoice() : nullptr;
+	const auto price = invoice->isPaidMedia ? invoice->amount : 0;
+	if (!price) {
+		return;
+	}
+
+	auto text = Ui::Text::String();
+	text.setText(
+		st::semiboldTextStyle,
+		tr::lng_paid_price(
+			tr::now,
+			lt_price,
+			QChar(0x2B50) + Lang::FormatCountDecimal(invoice->amount)));
+	const auto width = text.maxWidth();
+	const auto inner = QRect(0, 0, width, text.minHeight());
+	const auto outer = inner.marginsAdded(st::paidTagPadding);
+	const auto size = outer.size();
+
+	auto hq = PainterHighQualityEnabler(p);
+	p.setBrush(st::toastBg);
+	p.setPen(Qt::NoPen);
+
+	const auto radius = std::min(size.width(), size.height()) / 2.;
+	const auto rect = QRect(
+		rthumb.x() + (rthumb.width() - size.width()) / 2,
+		rthumb.y() + (rthumb.height() - size.height()) / 2,
+		size.width(),
+		size.height());
+
+	p.drawRoundedRect(rect, radius, radius);
+	p.setPen(st::toastFg);
+
+	text.draw(p, rect.x() - outer.x(), rect.y() - outer.y(), width);
+}
+
 void Photo::validateUserpicImageCache(QSize size, bool forum) const {
 	const auto forumValue = forum ? 1 : 0;
 	const auto large = _dataMedia->image(PhotoSize::Large);
@@ -604,6 +647,14 @@ QRect Photo::enlargeRect() const {
 	};
 }
 
+ClickHandlerPtr Photo::ensureExtendedMediaLink() const {
+	const auto item = parent()->data();
+	if (!_extendedMediaLink && item->isRegular()) {
+		_extendedMediaLink = MakePaidMediaLink(item);
+	}
+	return _extendedMediaLink;
+}
+
 TextState Photo::textState(QPoint point, StateRequest request) const {
 	auto result = TextState(_parent);
 
@@ -617,7 +668,9 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
 
 	if (QRect(paintx, painty, paintw, painth).contains(point)) {
 		ensureDataMediaCreated();
-		result.link = (_spoiler && !_spoiler->revealed)
+		result.link = _data->extendedMediaPreview()
+			? ensureExtendedMediaLink()
+			: (_spoiler && !_spoiler->revealed)
 			? _spoiler->link
 			: _data->uploading()
 			? _cancell
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h
index 44fbed00c..ad35e6481 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h
@@ -147,10 +147,13 @@ private:
 	[[nodiscard]] QSize photoSize() const;
 	[[nodiscard]] QRect enlargeRect() const;
 
+	void paintPriceTag(Painter &p, QRect rthumb) const;
+	[[nodiscard]] ClickHandlerPtr ensureExtendedMediaLink() const;
 	void togglePollingStory(bool enabled) const;
 
 	const not_null<PhotoData*> _data;
 	const FullStoryId _storyId;
+	mutable ClickHandlerPtr _extendedMediaLink;
 	mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
 	mutable std::unique_ptr<Streamed> _streamed;
 	const std::unique_ptr<MediaSpoiler> _spoiler;
diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style
index 99d9b4288..fe1a7b5b6 100644
--- a/Telegram/SourceFiles/media/view/media_view.style
+++ b/Telegram/SourceFiles/media/view/media_view.style
@@ -638,6 +638,7 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) {
 		menuSpoilerOff: icon {{ "menu/spoiler_off", storiesComposeWhiteText }};
 		menuBelow: icon {{ "menu/link_below", storiesComposeWhiteText }};
 		menuAbove: icon {{ "menu/link_above", storiesComposeWhiteText }};
+		menuPrice: icon {{ "menu/earn", storiesComposeWhiteText }};
 
 		stripBubble: icon{
 			{ "chat/reactions_bubble_shadow", windowShadowFg },
diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp
index 58328da12..2ebfba197 100644
--- a/Telegram/SourceFiles/menu/menu_send.cpp
+++ b/Telegram/SourceFiles/menu/menu_send.cpp
@@ -678,6 +678,14 @@ FillMenuResult FillSendMenu(
 			}, details); },
 			above ? &icons.menuBelow : &icons.menuAbove);
 	}
+	if (details.price) {
+		menu->addAction(
+			((*details.price > 0)
+				? tr::lng_context_change_price(tr::now)
+				: tr::lng_context_make_paid(tr::now)),
+			[=] { action({ .type = ActionType::ChangePrice }, details); },
+			&icons.menuPrice);
+	}
 
 	using namespace HistoryView::Reactions;
 	const auto effect = std::make_shared<QPointer<EffectPreview>>();
diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h
index 2c6dbb9b7..5c3a99bae 100644
--- a/Telegram/SourceFiles/menu/menu_send.h
+++ b/Telegram/SourceFiles/menu/menu_send.h
@@ -53,6 +53,7 @@ struct Details {
 	Type type = Type::Disabled;
 	SpoilerState spoiler = SpoilerState::None;
 	CaptionState caption = CaptionState::None;
+	std::optional<uint64> price;
 	bool effectAllowed = false;
 };
 
@@ -69,6 +70,7 @@ enum class ActionType : uchar {
 	SpoilerOff,
 	CaptionUp,
 	CaptionDown,
+	ChangePrice,
 };
 struct Action {
 	using Type = ActionType;
diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp
index 112cdd576..aad25cdf5 100644
--- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp
+++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp
@@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "window/window_session_controller.h"
 
 namespace Payments {
-namespace {
 
 bool IsCreditsInvoice(not_null<HistoryItem*> item) {
 	if (const auto payment = item->Get<HistoryServicePayment>()) {
@@ -38,8 +37,6 @@ bool IsCreditsInvoice(not_null<HistoryItem*> item) {
 	return invoice && (invoice->currency == Ui::kCreditsCurrency);
 }
 
-} // namespace
-
 Fn<void(NonPanelPaymentForm)> ProcessNonPanelPaymentFormFactory(
 		not_null<Window::SessionController*> controller,
 		Fn<void(CheckoutResult)> maybeReturnToBot) {
diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.h b/Telegram/SourceFiles/payments/payments_non_panel_process.h
index fb647a72a..e8ab9375c 100644
--- a/Telegram/SourceFiles/payments/payments_non_panel_process.h
+++ b/Telegram/SourceFiles/payments/payments_non_panel_process.h
@@ -18,6 +18,8 @@ namespace Payments {
 enum class CheckoutResult;
 struct NonPanelPaymentForm;
 
+[[nodiscard]] bool IsCreditsInvoice(not_null<HistoryItem*> item);
+
 Fn<void(NonPanelPaymentForm)> ProcessNonPanelPaymentFormFactory(
 	not_null<Window::SessionController*> controller,
 	Fn<void(Payments::CheckoutResult)> maybeReturnToBot = nullptr);
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp
index 01b94a9d1..b142a61c1 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp
@@ -32,9 +32,11 @@ constexpr auto kMinPreviewWidth = 20;
 AbstractSingleMediaPreview::AbstractSingleMediaPreview(
 	QWidget *parent,
 	const style::ComposeControls &st,
-	AttachControls::Type type)
+	AttachControls::Type type,
+	Fn<bool()> canToggleSpoiler)
 : AbstractSinglePreview(parent)
 , _st(st)
+, _canToggleSpoiler(std::move(canToggleSpoiler))
 , _minThumbH(st::sendBoxAlbumGroupSize.height()
 	+ st::sendBoxAlbumGroupSkipTop * 2)
 , _controls(base::make_unique_q<AttachControlsWidget>(this, type)) {
@@ -266,7 +268,9 @@ void AbstractSingleMediaPreview::applyCursor(style::cursor cursor) {
 }
 
 void AbstractSingleMediaPreview::showContextMenu(QPoint position) {
-	if (!_sendWay.sendImagesAsPhotos() || !supportsSpoilers()) {
+	if (!_canToggleSpoiler()
+		|| !_sendWay.sendImagesAsPhotos()
+		|| !supportsSpoilers()) {
 		return;
 	}
 	_menu = base::make_unique_q<Ui::PopupMenu>(
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h
index 0f5f8e784..fd72facf1 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h
@@ -26,7 +26,8 @@ public:
 	AbstractSingleMediaPreview(
 		QWidget *parent,
 		const style::ComposeControls &st,
-		AttachControls::Type type);
+		AttachControls::Type type,
+		Fn<bool()> canToggleSpoiler);
 	~AbstractSingleMediaPreview();
 
 	void setSendWay(SendFilesWay way);
@@ -71,6 +72,7 @@ private:
 
 	const style::ComposeControls &_st;
 	SendFilesWay _sendWay;
+	Fn<bool()> _canToggleSpoiler;
 	bool _animated = false;
 	QPixmap _preview;
 	QPixmap _previewBlurred;
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp
index 9b842aae5..76ff1ab2b 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp
@@ -31,10 +31,12 @@ AlbumPreview::AlbumPreview(
 	QWidget *parent,
 	const style::ComposeControls &st,
 	gsl::span<Ui::PreparedFile> items,
-	SendFilesWay way)
+	SendFilesWay way,
+	Fn<bool()> canToggleSpoiler)
 : RpWidget(parent)
 , _st(st)
 , _sendWay(way)
+, _canToggleSpoiler(std::move(canToggleSpoiler))
 , _dragTimer([=] { switchToDrag(); }) {
 	setMouseTracking(true);
 	prepareThumbs(items);
@@ -573,7 +575,7 @@ void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
 void AlbumPreview::showContextMenu(
 		not_null<AlbumThumbnail*> thumb,
 		QPoint position) {
-	if (!_sendWay.sendImagesAsPhotos()) {
+	if (!_canToggleSpoiler() || !_sendWay.sendImagesAsPhotos()) {
 		return;
 	}
 	_menu = base::make_unique_q<Ui::PopupMenu>(
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h
index 11eda122f..f51eb26e9 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.h
@@ -28,7 +28,8 @@ public:
 		QWidget *parent,
 		const style::ComposeControls &st,
 		gsl::span<Ui::PreparedFile> items,
-		SendFilesWay way);
+		SendFilesWay way,
+		Fn<bool()> canToggleSpoiler);
 	~AlbumPreview();
 
 	void setSendWay(SendFilesWay way);
@@ -92,6 +93,7 @@ private:
 
 	const style::ComposeControls &_st;
 	SendFilesWay _sendWay;
+	Fn<bool()> _canToggleSpoiler;
 	style::cursor _cursor = style::cur_default;
 	std::vector<int> _order;
 	std::vector<QSize> _itemsShownDimensions;
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp
index b686ee6fe..d180059dd 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_item_single_media_preview.cpp
@@ -36,7 +36,7 @@ ItemSingleMediaPreview::ItemSingleMediaPreview(
 	Fn<bool()> gifPaused,
 	not_null<HistoryItem*> item,
 	AttachControls::Type type)
-: AbstractSingleMediaPreview(parent, st, type)
+: AbstractSingleMediaPreview(parent, st, type, [] { return true; })
 , _gifPaused(std::move(gifPaused))
 , _fullId(item->fullId()) {
 	const auto media = item->media();
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp
index 1a70c69b6..e3f90912f 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp
@@ -195,6 +195,10 @@ bool PreparedList::canMoveCaption(bool sendingAlbum, bool compress) const {
 		|| (file.type == PreparedFile::Type::Photo && compress);
 }
 
+bool PreparedList::canChangePrice(bool sendingAlbum, bool compress) const {
+	return canMoveCaption(sendingAlbum, compress);
+}
+
 bool PreparedList::hasGroupOption(bool slowmode) const {
 	if (slowmode || files.size() < 2) {
 		return false;
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h
index 30210e3d6..49fd71fa1 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h
@@ -115,6 +115,9 @@ struct PreparedList {
 	[[nodiscard]] bool canMoveCaption(
 		bool sendingAlbum,
 		bool compress) const;
+	[[nodiscard]] bool canChangePrice(
+		bool sendingAlbum,
+		bool compress) const;
 	[[nodiscard]] bool canBeSentInSlowmode() const;
 	[[nodiscard]] bool canBeSentInSlowmodeWith(
 		const PreparedList &other) const;
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp
index ed250540c..cb80676cd 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.cpp
@@ -19,6 +19,7 @@ SingleMediaPreview *SingleMediaPreview::Create(
 		const style::ComposeControls &st,
 		Fn<bool()> gifPaused,
 		const PreparedFile &file,
+		Fn<bool()> canToggleSpoiler,
 		AttachControls::Type type) {
 	auto preview = QImage();
 	auto animated = false;
@@ -51,7 +52,8 @@ SingleMediaPreview *SingleMediaPreview::Create(
 		Core::IsMimeSticker(file.information->filemime),
 		file.spoiler,
 		animationPreview ? file.path : QString(),
-		type);
+		type,
+		std::move(canToggleSpoiler));
 }
 
 SingleMediaPreview::SingleMediaPreview(
@@ -63,8 +65,9 @@ SingleMediaPreview::SingleMediaPreview(
 	bool sticker,
 	bool spoiler,
 	const QString &animatedPreviewPath,
-	AttachControls::Type type)
-: AbstractSingleMediaPreview(parent, st, type)
+	AttachControls::Type type,
+	Fn<bool()> canToggleSpoiler)
+: AbstractSingleMediaPreview(parent, st, type, std::move(canToggleSpoiler))
 , _gifPaused(std::move(gifPaused))
 , _sticker(sticker) {
 	Expects(!preview.isNull());
diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h
index ac5d4f84f..d57ccd366 100644
--- a/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h
+++ b/Telegram/SourceFiles/ui/chat/attach/attach_single_media_preview.h
@@ -25,6 +25,7 @@ public:
 		const style::ComposeControls &st,
 		Fn<bool()> gifPaused,
 		const PreparedFile &file,
+		Fn<bool()> canToggleSpoiler,
 		AttachControls::Type type = AttachControls::Type::Full);
 
 	SingleMediaPreview(
@@ -36,7 +37,8 @@ public:
 		bool sticker,
 		bool spoiler,
 		const QString &animatedPreviewPath,
-		AttachControls::Type type);
+		AttachControls::Type type,
+		Fn<bool()> canToggleSpoiler);
 
 protected:
 	bool supportsSpoilers() const override;