From f3f660a1802435dcafcad99829370a6c224d7ce0 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 18 Jan 2024 10:39:00 +0400
Subject: [PATCH] Show toast on share attempt to premium required.

---
 Telegram/SourceFiles/boxes/peer_list_box.cpp  |  19 +-
 Telegram/SourceFiles/boxes/peer_list_box.h    |  10 ++
 .../boxes/peer_list_controllers.cpp           |  77 +++++----
 .../SourceFiles/boxes/peer_list_controllers.h |  15 ++
 Telegram/SourceFiles/boxes/peer_lists_box.cpp |   4 +-
 .../boxes/peers/edit_peer_invite_link.cpp     |   6 +
 Telegram/SourceFiles/boxes/share_box.cpp      | 162 +++++++++++++++---
 Telegram/SourceFiles/boxes/share_box.h        |   7 +
 .../calls/group/calls_group_settings.cpp      |   7 +
 Telegram/SourceFiles/data/data_user.cpp       |   4 +
 Telegram/SourceFiles/data/data_user.h         |   1 +
 .../media/stories/media_stories_share.cpp     |   7 +
 12 files changed, 255 insertions(+), 64 deletions(-)

diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index 3278088ec..fe3a3f28e 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -232,9 +232,7 @@ void PeerListBox::resizeEvent(QResizeEvent *e) {
 void PeerListBox::paintEvent(QPaintEvent *e) {
 	auto p = QPainter(this);
 
-	const auto &bg = (_controller->listSt()
-		? *_controller->listSt()
-		: st::peerListBox).bg;
+	const auto &bg = _controller->computeListSt().bg;
 	const auto fill = QRect(
 		0,
 		_addedTopScrollSkip,
@@ -1255,6 +1253,16 @@ not_null<PeerListRow*> PeerListContent::rowAt(int index) const {
 	return _rows[index].get();
 }
 
+int PeerListContent::searchRowsCount() const {
+	return _searchRows.size();
+}
+
+not_null<PeerListRow*> PeerListContent::searchRowAt(int index) const {
+	Expects(index >= 0 && index < _searchRows.size());
+
+	return _searchRows[index].get();
+}
+
 void PeerListContent::setDescription(object_ptr<Ui::FlatLabel> description) {
 	_description = std::move(description);
 	if (_description) {
@@ -1349,6 +1357,7 @@ void PeerListContent::refreshRows() {
 	if (_mouseSelection) {
 		selectByMouse(QCursor::pos());
 	}
+	loadProfilePhotos();
 	update();
 }
 
@@ -1903,7 +1912,9 @@ void PeerListContent::mouseLeftGeometry() {
 }
 
 void PeerListContent::loadProfilePhotos() {
-	if (_visibleTop >= _visibleBottom) return;
+	if (_visibleTop >= _visibleBottom) {
+		return;
+	}
 
 	auto yFrom = _visibleTop;
 	auto yTo = _visibleBottom + (_visibleBottom - _visibleTop) * PreloadHeightsCount;
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h
index 25e96f60b..50a37263e 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.h
+++ b/Telegram/SourceFiles/boxes/peer_list_box.h
@@ -332,6 +332,8 @@ public:
 	virtual void peerListScrollToTop() = 0;
 	virtual int peerListFullRowsCount() = 0;
 	virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0;
+	virtual int peerListSearchRowsCount() = 0;
+	virtual not_null<PeerListRow*> peerListSearchRowAt(int index) = 0;
 	virtual std::optional<QPoint> peerListLastRowMousePosition() = 0;
 	virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
 	virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
@@ -627,6 +629,8 @@ public:
 	void convertRowToSearchResult(not_null<PeerListRow*> row);
 	int fullRowsCount() const;
 	not_null<PeerListRow*> rowAt(int index) const;
+	int searchRowsCount() const;
+	not_null<PeerListRow*> searchRowAt(int index) const;
 	void setDescription(object_ptr<Ui::FlatLabel> description);
 	void setSearchLoading(object_ptr<Ui::FlatLabel> loading);
 	void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults);
@@ -908,6 +912,12 @@ public:
 	not_null<PeerListRow*> peerListRowAt(int index) override {
 		return _content->rowAt(index);
 	}
+	int peerListSearchRowsCount() override {
+		return _content->searchRowsCount();
+	}
+	not_null<PeerListRow*> peerListSearchRowAt(int index) override {
+		return _content->searchRowAt(index);
+	}
 	void peerListRefreshRows() override {
 		_content->refreshRows();
 	}
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
index 7d8c2d291..382301725 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
@@ -271,39 +271,17 @@ ChatsListBoxController::Row::Row(
 , _delegate(delegate) {
 }
 
-void PaintLock(
-		Painter &p,
-		not_null<const style::PeerListItem*> st,
-		int x,
-		int y,
-		int outerWidth,
-		int size) {
-	auto hq = PainterHighQualityEnabler(p);
-	const auto &check = st->checkbox.check;
-	auto pen = check.border->p;
-	pen.setWidthF(check.width);
-	p.setPen(pen);
-	p.setBrush(st::premiumButtonBg2);
-	const auto &icon = st::stickersPremiumLock;
-	const auto width = icon.width();
-	const auto height = icon.height();
-	const auto rect = QRect(
-		QPoint(x + size - width, y + size - height),
-		icon.size());
-	p.drawEllipse(rect);
-	icon.paintInCenter(p, rect);
-}
-
 auto ChatsListBoxController::Row::generatePaintUserpicCallback(
 	bool forceRound)
 -> PaintRoundImageCallback {
 	auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
 	if (_locked) {
-		AssertIsDebug();
-		const auto st = /*_delegate ? _delegate->rowSt() : */&st::defaultPeerListItem;
+		const auto st = _delegate
+			? _delegate->rowSt().get()
+			: &st::defaultPeerListItem;
 		return [=](Painter &p, int x, int y, int outerWidth, int size) {
 			result(p, x, y, outerWidth, size);
-			PaintLock(p, st, x, y, outerWidth, size);
+			PaintPremiumRequiredLock(p, st, x, y, outerWidth, size);
 		};
 	}
 	return result;
@@ -475,7 +453,7 @@ void PeerListStories::process(not_null<PeerListRow*> row) {
 
 bool PeerListStories::handleClick(not_null<PeerData*> peer) {
 	const auto point = _delegate->peerListLastRowMousePosition();
-	const auto &st = _controller->listSt()->item;
+	const auto &st = _controller->computeListSt().item;
 	if (point && point->x() < st.photoPosition.x() + st.photoSize) {
 		if (const auto window = peer->session().tryResolveWindow()) {
 			if (const auto user = peer->asUser()) {
@@ -492,9 +470,9 @@ bool PeerListStories::handleClick(not_null<PeerData*> peer) {
 void PeerListStories::prepare(not_null<PeerListDelegate*> delegate) {
 	_delegate = delegate;
 
-	_unreadBrush = PeerListStoriesGradient(*_controller->listSt());
+	_unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
 	style::PaletteChanged() | rpl::start_with_next([=] {
-		_unreadBrush = PeerListStoriesGradient(*_controller->listSt());
+		_unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
 		updateColors();
 	}, _lifetime);
 
@@ -738,9 +716,7 @@ void ChooseRecipientBoxController::prepareViewHook() {
 }
 
 void ChooseRecipientBoxController::refreshLockedRows() {
-	auto count = delegate()->peerListFullRowsCount();
-	for (auto i = 0; i != count; ++i) {
-		const auto raw = delegate()->peerListRowAt(i);
+	const auto process = [&](not_null<PeerListRow*> raw) {
 		const auto row = static_cast<Row*>(raw.get());
 		if (const auto user = row->peer()->asUser()) {
 			const auto history = row->history();
@@ -751,6 +727,14 @@ void ChooseRecipientBoxController::refreshLockedRows() {
 				delegate()->peerListUpdateRow(row);
 			}
 		}
+	};
+	auto count = delegate()->peerListFullRowsCount();
+	for (auto i = 0; i != count; ++i) {
+		process(delegate()->peerListRowAt(i));
+	}
+	count = delegate()->peerListSearchRowsCount();
+	for (auto i = 0; i != count; ++i) {
+		process(delegate()->peerListSearchRowAt(i));
 	}
 }
 
@@ -766,6 +750,10 @@ void ChooseRecipientBoxController::rowPreloadUserpic(not_null<Row*> row) {
 	}
 }
 
+not_null<const style::PeerListItem*> ChooseRecipientBoxController::rowSt() {
+	return &computeListSt().item;
+}
+
 void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
 	if (showLockedError(row)) {
 		return;
@@ -845,7 +833,7 @@ auto ChooseRecipientBoxController::createRow(
 		: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
 			|| peer->isRepliesChat()
 			|| (peer->isUser() && (_premiumRequiredError
-				? peer->asUser()->isInaccessible()
+				? !peer->asUser()->canSendIgnoreRequirePremium()
 				: !Data::CanSendAnything(peer))));
 	if (skip) {
 		return nullptr;
@@ -1077,3 +1065,26 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
 	const auto skip = _filter && !_filter(topic);
 	return skip ? nullptr : std::make_unique<Row>(topic);
 };
+
+void PaintPremiumRequiredLock(
+		Painter &p,
+		not_null<const style::PeerListItem*> st,
+		int x,
+		int y,
+		int outerWidth,
+		int size) {
+	auto hq = PainterHighQualityEnabler(p);
+	const auto &check = st->checkbox.check;
+	auto pen = check.border->p;
+	pen.setWidthF(check.width);
+	p.setPen(pen);
+	p.setBrush(st::premiumButtonBg2);
+	const auto &icon = st::stickersPremiumLock;
+	const auto width = icon.width();
+	const auto height = icon.height();
+	const auto rect = QRect(
+		QPoint(x + size - width, y + size - height),
+		icon.size());
+	p.drawEllipse(rect);
+	icon.paintInCenter(p, rect);
+}
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h
index 9ce3dd0bf..73522ba4d 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.h
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h
@@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 class History;
 
+namespace style {
+struct PeerListItem;
+} // namespace style
+
 namespace Data {
 class Thread;
 class Forum;
@@ -95,6 +99,8 @@ public:
 	class RowDelegate {
 	public:
 		virtual void rowPreloadUserpic(not_null<Row*> row);
+		[[nodiscard]] virtual auto rowSt()
+			-> not_null<const style::PeerListItem*> = 0;
 	};
 
 	class Row : public PeerListRow {
@@ -266,6 +272,7 @@ protected:
 private:
 	void refreshLockedRows();
 	void rowPreloadUserpic(not_null<Row*> row) override;
+	not_null<const style::PeerListItem*> rowSt() override;
 
 	const not_null<Main::Session*> _session;
 	FnMut<void(not_null<Data::Thread*>)> _callback;
@@ -350,3 +357,11 @@ private:
 	Fn<bool(not_null<Data::ForumTopic*>)> _filter;
 
 };
+
+void PaintPremiumRequiredLock(
+	Painter &p,
+	not_null<const style::PeerListItem*> st,
+	int x,
+	int y,
+	int outerWidth,
+	int size);
diff --git a/Telegram/SourceFiles/boxes/peer_lists_box.cpp b/Telegram/SourceFiles/boxes/peer_lists_box.cpp
index 80e2a9334..e1ced558c 100644
--- a/Telegram/SourceFiles/boxes/peer_lists_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_lists_box.cpp
@@ -281,9 +281,7 @@ void PeerListsBox::resizeEvent(QResizeEvent *e) {
 void PeerListsBox::paintEvent(QPaintEvent *e) {
 	auto p = QPainter(this);
 
-	const auto &bg = (firstController()->listSt()
-		? *firstController()->listSt()
-		: st::peerListBox).bg;
+	const auto &bg = firstController()->computeListSt().bg;
 	for (const auto &rect : e->region()) {
 		p.fillRect(rect, bg);
 	}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
index 03bf6108d..d8adfbc41 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
@@ -1194,6 +1194,11 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
 		}
 	};
 	auto filterCallback = [](not_null<Data::Thread*> thread) {
+		if (const auto user = thread->peer()->asUser()) {
+			if (user->canSendIgnoreRequirePremium()) {
+				return true;
+			}
+		}
 		return Data::CanSendTexts(thread);
 	};
 	auto object = Box<ShareBox>(ShareBox::Descriptor{
@@ -1201,6 +1206,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
 		.copyCallback = std::move(copyCallback),
 		.submitCallback = std::move(submitCallback),
 		.filterCallback = std::move(filterCallback),
+		.premiumRequiredError = SharePremiumRequiredError(),
 	});
 	*box = Ui::MakeWeak(object.data());
 	return object;
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index 007355317..b5a549330 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "boxes/share_box.h"
 
+#include "api/api_premium.h"
 #include "base/random.h"
 #include "lang/lang_keys.h"
 #include "base/qthelp_url.h"
@@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "history/history_item_helpers.h"
 #include "history/view/history_view_element.h"
 #include "history/view/history_view_context_menu.h" // CopyPostLink.
+#include "settings/settings_premium.h"
 #include "window/window_session_controller.h"
 #include "boxes/peer_list_controllers.h"
 #include "chat_helpers/emoji_suggestions_widget.h"
@@ -38,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_game.h"
 #include "data/data_histories.h"
 #include "data/data_user.h"
+#include "data/data_peer_values.h"
 #include "data/data_session.h"
 #include "data/data_folder.h"
 #include "data/data_forum.h"
@@ -96,19 +99,23 @@ protected:
 private:
 	struct Chat {
 		Chat(
-			not_null<PeerData*> peer,
+			not_null<History*> history,
 			const style::PeerListItem &st,
 			Fn<void()> updateCallback);
 
+		not_null<History*> history;
 		not_null<PeerData*> peer;
 		Data::ForumTopic *topic = nullptr;
 		rpl::lifetime topicLifetime;
 		Ui::RoundImageCheckbox checkbox;
 		Ui::Text::String name;
 		Ui::Animations::Simple nameActive;
+		bool locked = false;
 	};
 
 	void invalidateCache();
+	bool showLockedError(not_null<Chat*> chat);
+	void refreshLockedRows();
 
 	[[nodiscard]] int displayedChatsCount() const;
 	[[nodiscard]] not_null<Data::Thread*> chatThread(
@@ -117,12 +124,14 @@ private:
 	void paintChat(Painter &p, not_null<Chat*> chat, int index);
 	void updateChat(not_null<PeerData*> peer);
 	void updateChatName(not_null<Chat*> chat);
+	void initChatLocked(not_null<Chat*> chat);
 	void repaintChat(not_null<PeerData*> peer);
 	int chatIndex(not_null<PeerData*> peer) const;
 	void repaintChatAtIndex(int index);
 	Chat *getChatAtIndex(int index);
 
-	void loadProfilePhotos(int yFrom);
+	void loadProfilePhotos();
+	void preloadUserpic(not_null<Dialogs::Entry*> entry);
 	void changeCheckState(Chat *chat);
 	void chooseForumTopic(not_null<Data::Forum*> forum);
 	enum class ChangeStateWay {
@@ -153,6 +162,7 @@ private:
 	int _columnCount = 4;
 	int _active = -1;
 	int _upon = -1;
+	int _visibleTop = 0;
 
 	std::unique_ptr<Dialogs::IndexedList> _chatsIndexed;
 	QString _filter;
@@ -649,6 +659,16 @@ ShareBox::Inner::Inner(
 	_rowHeight = st::shareRowHeight;
 	setAttribute(Qt::WA_OpaquePaintEvent);
 
+	if (_descriptor.premiumRequiredError) {
+		const auto session = _descriptor.session;
+		rpl::merge(
+			Data::AmPremiumValue(session) | rpl::to_empty,
+			session->api().premium().somePremiumRequiredResolved()
+		) | rpl::start_with_next([=] {
+			refreshLockedRows();
+		}, lifetime());
+	}
+
 	const auto self = _descriptor.session->user();
 	const auto selfHistory = self->owner().history(self);
 	if (_descriptor.filterCallback(selfHistory)) {
@@ -705,10 +725,48 @@ void ShareBox::Inner::invalidateCache() {
 	}
 }
 
+bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
+	if (!chat->locked) {
+		return false;
+	}
+	::Settings::ShowPremiumPromoToast(
+		Main::MakeSessionShow(_show, _descriptor.session),
+		ChatHelpers::ResolveWindowDefault(),
+		_descriptor.premiumRequiredError(chat->peer->asUser()).text,
+		u"require_premium"_q);
+	return true;
+}
+
+void ShareBox::Inner::refreshLockedRows() {
+	auto changed = false;
+	for (const auto &[peer, data] : _dataMap) {
+		const auto history = data->history;
+		const auto locked = (Api::ResolveRequiresPremiumToWrite(history)
+			== Api::RequirePremiumState::Yes);
+		if (data->locked != locked) {
+			data->locked = locked;
+			changed = true;
+		}
+	}
+	for (const auto &data : d_byUsernameFiltered) {
+		const auto history = data->history;
+		const auto locked = (Api::ResolveRequiresPremiumToWrite(history)
+			== Api::RequirePremiumState::Yes);
+		if (data->locked != locked) {
+			data->locked = locked;
+			changed = true;
+		}
+	}
+	if (changed) {
+		update();
+	}
+}
+
 void ShareBox::Inner::visibleTopBottomUpdated(
 		int visibleTop,
 		int visibleBottom) {
-	loadProfilePhotos(visibleTop);
+	_visibleTop = visibleTop;
+	loadProfilePhotos();
 }
 
 void ShareBox::Inner::activateSkipRow(int direction) {
@@ -760,6 +818,16 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
 	chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
 }
 
+void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) {
+	if (_descriptor.premiumRequiredError) {
+		const auto history = chat->history;
+		const auto require = Api::ResolveRequiresPremiumToWrite(history);
+		if (require == Api::RequirePremiumState::Yes) {
+			chat->locked = true;
+		}
+	}
+}
+
 void ShareBox::Inner::repaintChatAtIndex(int index) {
 	if (index < 0) return;
 
@@ -829,11 +897,11 @@ int ShareBox::Inner::chatIndex(not_null<PeerData*> peer) const {
 	return -1;
 }
 
-void ShareBox::Inner::loadProfilePhotos(int yFrom) {
-	if (!parentWidget()) return;
-	if (yFrom < 0) {
-		yFrom = 0;
+void ShareBox::Inner::loadProfilePhotos() {
+	if (!parentWidget()) {
+		return;
 	}
+	auto yFrom = std::max(_visibleTop, 0);
 	if (auto part = (yFrom % _rowHeight)) {
 		yFrom -= part;
 	}
@@ -853,20 +921,38 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
 				if (((*i)->index() * _rowHeight) >= yTo) {
 					break;
 				}
-				(*i)->entry()->chatListPreloadData();
+				preloadUserpic((*i)->entry());
 			}
 		}
-	} else if (!_filtered.empty()) {
-		int from = yFrom / _rowHeight;
-		if (from < 0) from = 0;
-		if (from < _filtered.size()) {
-			int to = (yTo / _rowHeight) + 1;
-			if (to > _filtered.size()) to = _filtered.size();
+	} else {
+		const auto from = std::max(yFrom / _rowHeight, 0);
+		const auto to = std::max((yTo / _rowHeight) + 1, from);
 
-			for (; from < to; ++from) {
-				_filtered[from]->entry()->chatListPreloadData();
-			}
+		const auto ffrom = from;
+		const auto fto = std::min(to, int(_filtered.size()));
+		for (auto i = ffrom; i != fto; ++i) {
+			preloadUserpic(_filtered[i]->entry());
 		}
+
+		const auto ufrom = std::max(from - int(_filtered.size()), 0);
+		const auto uto = std::min(
+			to - int(_filtered.size()),
+			int(d_byUsernameFiltered.size()));
+		for (auto i = ufrom; i != uto; ++i) {
+			preloadUserpic(d_byUsernameFiltered[i]->history);
+		}
+	}
+}
+
+void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
+	entry->chatListPreloadData();
+	const auto history = entry->asHistory();
+	if (!_descriptor.premiumRequiredError || !history) {
+		return;
+	} else if (Api::ResolveRequiresPremiumToWrite(history)
+		== Api::RequirePremiumState::Unknown) {
+		const auto user = history->peer->asUser();
+		_descriptor.session->api().premium().resolvePremiumRequired(user);
 	}
 }
 
@@ -877,15 +963,19 @@ auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
 	if (const auto data = static_cast<Chat*>(row->attached)) {
 		return data;
 	}
-	const auto peer = row->history()->peer;
+	const auto history = row->history();
+	const auto peer = history->peer;
 	if (const auto i = _dataMap.find(peer); i != end(_dataMap)) {
 		row->attached = i->second.get();
 		return i->second.get();
 	}
 	const auto &[i, ok] = _dataMap.emplace(
 		peer,
-		std::make_unique<Chat>(peer, _st.item, [=] { repaintChat(peer); }));
+		std::make_unique<Chat>(history, _st.item, [=] {
+			repaintChat(peer);
+		}));
 	updateChatName(i->second.get());
+	initChatLocked(i->second.get());
 	row->attached = i->second.get();
 	return i->second.get();
 }
@@ -919,6 +1009,16 @@ void ShareBox::Inner::paintChat(
 	auto photoTop = st::sharePhotoTop;
 	chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
 
+	if (chat->locked) {
+		PaintPremiumRequiredLock(
+			p,
+			&_st.item,
+			x + photoLeft,
+			y + photoTop,
+			outerWidth,
+			_st.item.checkbox.imageRadius * 2);
+	}
+
 	auto nameActive = chat->nameActive.value((index == _active) ? 1. : 0.);
 	p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameActive));
 
@@ -929,10 +1029,11 @@ void ShareBox::Inner::paintChat(
 }
 
 ShareBox::Inner::Chat::Chat(
-	not_null<PeerData*> peer,
+	not_null<History*> history,
 	const style::PeerListItem &st,
 	Fn<void()> updateCallback)
-: peer(peer)
+: history(history)
+, peer(history->peer)
 , checkbox(
 	st.checkbox,
 	updateCallback,
@@ -1062,7 +1163,7 @@ void ShareBox::Inner::resizeEvent(QResizeEvent *e) {
 }
 
 void ShareBox::Inner::changeCheckState(Chat *chat) {
-	if (!chat) {
+	if (!chat || showLockedError(chat)) {
 		return;
 	} else if (!_filter.isEmpty()) {
 		const auto history = chat->peer->owner().history(chat->peer);
@@ -1194,8 +1295,8 @@ void ShareBox::Inner::updateFilter(QString filter) {
 			_searchRequests.fire({});
 		}
 		setActive(-1);
+		loadProfilePhotos();
 		update();
-		loadProfilePhotos(0);
 	}
 }
 
@@ -1234,10 +1335,11 @@ void ShareBox::Inner::peopleReceived(
 				}
 				_byUsernameFiltered.push_back(peer);
 				d_byUsernameFiltered.push_back(std::make_unique<Chat>(
-					peer,
+					history,
 					_st.item,
 					[=] { repaintChat(peer); }));
 				updateChatName(d_byUsernameFiltered.back().get());
+				initChatLocked(d_byUsernameFiltered.back().get());
 			}
 		}
 	};
@@ -1256,6 +1358,7 @@ void ShareBox::Inner::refresh() {
 	} else {
 		resize(width(), st::noContactsHeight);
 	}
+	loadProfilePhotos();
 	update();
 }
 
@@ -1525,6 +1628,11 @@ void FastShareMessage(
 	const auto requiredRight = item->requiredSendRight();
 	const auto requiresInline = item->requiresSendInlineRight();
 	auto filterCallback = [=](not_null<Data::Thread*> thread) {
+		if (const auto user = thread->peer()->asUser()) {
+			if (user->canSendIgnoreRequirePremium()) {
+				return true;
+			}
+		}
 		return Data::CanSend(thread, requiredRight)
 			&& (!requiresInline
 				|| Data::CanSend(thread, ChatRestriction::SendInline))
@@ -1547,10 +1655,16 @@ void FastShareMessage(
 				.show = !hasOnlyForcedForwardedInfo,
 				.hasCaptions = hasCaptions,
 			},
+			.premiumRequiredError = SharePremiumRequiredError(),
 		}),
 		Ui::LayerOption::CloseOther);
 }
 
+auto SharePremiumRequiredError()
+-> Fn<ChooseRecipientPremiumRequiredError(not_null<UserData*>)> {
+	return WritePremiumRequiredError;
+}
+
 void ShareGameScoreByHash(
 		not_null<Window::SessionController*> controller,
 		const QString &hash) {
diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h
index af6bde3a1..00314f3ad 100644
--- a/Telegram/SourceFiles/boxes/share_box.h
+++ b/Telegram/SourceFiles/boxes/share_box.h
@@ -69,6 +69,10 @@ void FastShareMessage(
 	not_null<Window::SessionController*> controller,
 	not_null<HistoryItem*> item);
 
+struct ChooseRecipientPremiumRequiredError;
+[[nodiscard]] auto SharePremiumRequiredError()
+-> Fn<ChooseRecipientPremiumRequiredError(not_null<UserData*>)>;
+
 class ShareBox final : public Ui::BoxContent {
 public:
 	using CopyCallback = Fn<void()>;
@@ -101,6 +105,9 @@ public:
 			bool hasCaptions = false;
 		} forwardOptions;
 		HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
+
+		using PremiumRequiredError = ChooseRecipientPremiumRequiredError;
+		Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
 	};
 	ShareBox(QWidget*, Descriptor &&descriptor);
 
diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp
index 8e0df2f3e..4a7b50458 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_channel.h"
 #include "data/data_chat.h"
 #include "data/data_group_call.h"
+#include "data/data_user.h"
 #include "calls/group/calls_group_rtmp.h"
 #include "ui/toast/toast.h"
 #include "data/data_changes.h"
@@ -191,6 +192,11 @@ object_ptr<ShareBox> ShareInviteLinkBox(
 		show->showToast(tr::lng_share_done(tr::now));
 	};
 	auto filterCallback = [](not_null<Data::Thread*> thread) {
+		if (const auto user = thread->peer()->asUser()) {
+			if (user->canSendIgnoreRequirePremium()) {
+				return true;
+			}
+		}
 		return Data::CanSend(thread, ChatRestriction::SendOther);
 	};
 
@@ -227,6 +233,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
 		.st = &st::groupCallShareBoxList,
 		.stLabel = &st::groupCallField,
 		.scheduleBoxStyle = scheduleStyle(),
+		.premiumRequiredError = SharePremiumRequiredError(),
 	});
 	*box = result.data();
 	return result;
diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index 4e5029afe..5014bdfb9 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -402,6 +402,10 @@ bool UserData::requirePremiumToWriteKnown() const {
 	return (flags() & UserDataFlag::RequirePremiumToWriteKnown);
 }
 
+bool UserData::canSendIgnoreRequirePremium() const {
+	return !isInaccessible() && !isRepliesChat();
+}
+
 bool UserData::readDatesPrivate() const {
 	return (flags() & UserDataFlag::ReadDatesPrivate);
 }
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index ff3062b81..f6ba39749 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -131,6 +131,7 @@ public:
 	[[nodiscard]] bool someRequirePremiumToWrite() const;
 	[[nodiscard]] bool meRequiresPremiumToWrite() const;
 	[[nodiscard]] bool requirePremiumToWriteKnown() const;
+	[[nodiscard]] bool canSendIgnoreRequirePremium() const;
 	[[nodiscard]] bool readDatesPrivate() const;
 
 	[[nodiscard]] bool canShareThisContact() const;
diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp
index 91fc29002..deb291cc1 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "data/data_session.h"
 #include "data/data_stories.h"
 #include "data/data_thread.h"
+#include "data/data_user.h"
 #include "history/history.h"
 #include "history/history_item_helpers.h" // GetErrorTextForSending.
 #include "history/view/history_view_context_menu.h" // CopyStoryLink.
@@ -61,6 +62,11 @@ namespace Media::Stories {
 	};
 	const auto state = std::make_shared<State>();
 	auto filterCallback = [=](not_null<Data::Thread*> thread) {
+		if (const auto user = thread->peer()->asUser()) {
+			if (user->canSendIgnoreRequirePremium()) {
+				return true;
+			}
+		}
 		return Data::CanSend(thread, ChatRestriction::SendPhotos)
 			&& Data::CanSend(thread, ChatRestriction::SendVideos);
 	};
@@ -189,6 +195,7 @@ namespace Media::Stories {
 		.scheduleBoxStyle = (viewerStyle
 			? viewerScheduleStyle()
 			: HistoryView::ScheduleBoxStyleArgs()),
+		.premiumRequiredError = SharePremiumRequiredError(),
 	});
 }