From f0955f2021c209f60991b75c7056147ba9d88329 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 6 Sep 2022 11:20:55 +0400
Subject: [PATCH] Add emoji-status disclaimer for unknown peers.

---
 Telegram/Resources/langs/lang.strings         |   2 +
 .../calls/group/calls_choose_join_as.cpp      |   3 +-
 .../data/stickers/data_custom_emoji.cpp       |  19 +-
 .../dialogs/dialogs_inner_widget.cpp          |   3 +-
 .../export/view/export_view_settings.cpp      |  15 +-
 .../view/history_view_contact_status.cpp      | 197 ++++++++++++++++--
 .../view/history_view_contact_status.h        |   3 +
 .../info/profile/info_profile_actions.cpp     |   3 +-
 Telegram/SourceFiles/intro/intro_widget.cpp   |   9 +-
 .../passport/passport_panel_edit_contact.cpp  |   5 +-
 .../payments/ui/payments_form_summary.cpp     |   5 +-
 .../SourceFiles/settings/settings_main.cpp    |   3 +-
 .../settings/settings_privacy_controllers.cpp |  16 +-
 Telegram/SourceFiles/ui/chat/chat.style       |   4 +
 Telegram/lib_ui                               |   2 +-
 15 files changed, 223 insertions(+), 66 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 652097375..5695fc106 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1910,6 +1910,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_new_contact_unarchive" = "Unarchive";
 "lng_new_contact_from_request_channel" = "{user} is an admin of {name}, a channel you requested to join.";
 "lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join.";
+"lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}.";
+"lng_new_contact_about_status_link" = "Telegram Premium";
 "lng_from_request_title_channel" = "Chat with channel's admin";
 "lng_from_request_title_group" = "Chat with group's admin";
 "lng_from_request_body" = "You received this message because you requested to join {name} on {date}.";
diff --git a/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp b/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp
index c2b2121ee..2ac790288 100644
--- a/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp
@@ -247,12 +247,11 @@ void ChooseJoinAsBox(
 					: tr::lng_group_call_schedule)(makeLink),
 				Ui::Text::WithEntities),
 			labelSt));
-		label->setClickHandlerFilter([=](const auto&...) {
+		label->overrideLinkClickHandler([=] {
 			auto withJoinAs = info;
 			withJoinAs.joinAs = controller->selected();
 			box->getDelegate()->show(
 				Box(ScheduleGroupCallBox, withJoinAs, done));
-			return false;
 		});
 	}
 	auto next = (context == Context::Switch)
diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp
index d03df2e45..3473d697d 100644
--- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp
+++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp
@@ -758,17 +758,26 @@ void CustomEmojiManager::scheduleRepaintTimer() {
 void CustomEmojiManager::invokeRepaints() {
 	_repaintNext = 0;
 	const auto now = crl::now();
+	auto repaint = std::vector<base::weak_ptr<Ui::CustomEmoji::Instance>>();
 	for (auto i = begin(_repaints); i != end(_repaints);) {
 		if (i->second.when > now) {
 			++i;
 			continue;
 		}
-		auto bunch = std::move(i->second);
+		auto &list = i->second.instances;
+		if (repaint.empty()) {
+			repaint = std::move(list);
+		} else {
+			repaint.insert(
+				end(repaint),
+				std::make_move_iterator(begin(list)),
+				std::make_move_iterator(end(list)));
+		}
 		i = _repaints.erase(i);
-		for (const auto &weak : bunch.instances) {
-			if (const auto strong = weak.get()) {
-				strong->repaint();
-			}
+	}
+	for (const auto &weak : repaint) {
+		if (const auto strong = weak.get()) {
+			strong->repaint();
 		}
 	}
 	scheduleRepaintTimer();
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 90479b3b7..ffd7002bf 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -2367,13 +2367,12 @@ void InnerWidget::refreshEmptyLabel() {
 	});
 	_empty.create(this, std::move(full), st::dialogsEmptyLabel);
 	resizeEmptyLabel();
-	_empty->setClickHandlerFilter([=](const auto &...) {
+	_empty->overrideLinkClickHandler([=] {
 		if (_emptyState == EmptyState::NoContacts) {
 			_controller->showAddContact();
 		} else if (_emptyState == EmptyState::EmptyFolder) {
 			editOpenedFilter();
 		}
-		return false;
 	});
 	_empty->setVisible(_state == WidgetState::Default);
 }
diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp
index 497061379..35c6c5e3a 100644
--- a/Telegram/SourceFiles/export/view/export_view_settings.cpp
+++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp
@@ -300,9 +300,8 @@ void SettingsWidget::addLocationLabel(
 				Ui::Text::WithEntities),
 			st::exportLocationLabel),
 		st::exportLocationPadding);
-	label->setClickHandlerFilter([=](auto&&...) {
+	label->overrideLinkClickHandler([=] {
 		chooseFolder();
-		return false;
 	});
 #endif // OS_MAC_STORE
 }
@@ -357,10 +356,7 @@ void SettingsWidget::addFormatAndLocationLabel(
 				Ui::Text::WithEntities),
 			st::exportLocationLabel),
 		st::exportLocationPadding);
-	label->setClickHandlerFilter([=](
-		const ClickHandlerPtr &handler,
-		Qt::MouseButton) {
-		const auto url = handler->dragText();
+	label->overrideLinkClickHandler([=](const QString &url) {
 		if (url == qstr("internal:edit_export_path")) {
 			chooseFolder();
 		} else if (url == qstr("internal:edit_format")) {
@@ -368,7 +364,6 @@ void SettingsWidget::addFormatAndLocationLabel(
 		} else {
 			Unexpected("Click handler URL in export limits edit.");
 		}
-		return false;
 	});
 #endif // OS_MAC_STORE
 }
@@ -413,10 +408,7 @@ void SettingsWidget::addLimitsLabel(
 			std::move(datesText),
 			st::exportLocationLabel),
 		st::exportLimitsPadding);
-	label->setClickHandlerFilter([=](
-			const ClickHandlerPtr &handler,
-			Qt::MouseButton) {
-		const auto url = handler->dragText();
+	label->overrideLinkClickHandler([=](const QString &url) {
 		if (url == qstr("internal:edit_from")) {
 			const auto done = [=](TimeId limit) {
 				changeData([&](Settings &settings) {
@@ -444,7 +436,6 @@ void SettingsWidget::addLimitsLabel(
 		} else {
 			Unexpected("Click handler URL in export limits edit.");
 		}
-		return false;
 	});
 }
 
diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
index 490712ac4..ddb3b4ae3 100644
--- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
@@ -11,18 +11,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/checkbox.h"
 #include "ui/widgets/labels.h"
+#include "ui/wrap/padding_wrap.h"
 #include "ui/layers/generic_box.h"
 #include "ui/toast/toast.h"
 #include "ui/text/format_values.h" // Ui::FormatPhone
 #include "ui/text/text_utilities.h"
 #include "ui/boxes/confirm_box.h"
 #include "ui/layers/generic_box.h"
+#include "core/ui_integration.h"
 #include "data/notify/data_notify_settings.h"
 #include "data/data_peer.h"
 #include "data/data_user.h"
 #include "data/data_chat.h"
 #include "data/data_channel.h"
+#include "data/data_changes.h"
+#include "data/data_document.h"
 #include "data/data_session.h"
+#include "data/stickers/data_custom_emoji.h"
+#include "settings/settings_premium.h"
 #include "window/window_peer_menu.h"
 #include "window/window_controller.h"
 #include "window/window_session_controller.h"
@@ -38,7 +44,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace HistoryView {
 namespace {
 
-bool BarCurrentlyHidden(not_null<PeerData*> peer) {
+class Listener final : public Data::CustomEmojiManager::Listener {
+public:
+	explicit Listener(Fn<void(not_null<DocumentData*>)> check)
+	: _check(std::move(check)) {
+	}
+
+	void customEmojiResolveDone(
+		not_null<DocumentData*> document) override {
+		_check(document);
+	}
+
+private:
+	Fn<void(not_null<DocumentData*>)> _check;
+
+};
+
+[[nodiscard]] bool BarCurrentlyHidden(not_null<PeerData*> peer) {
 	const auto settings = peer->settings();
 	if (!settings) {
 		return false;
@@ -58,6 +80,66 @@ bool BarCurrentlyHidden(not_null<PeerData*> peer) {
 	return false;
 }
 
+[[nodiscard]] rpl::producer<TextWithEntities> ResolveIsCustom(
+		not_null<Data::Session*> owner,
+		DocumentId id) {
+	return [=](auto consumer) {
+		const auto document = owner->document(id);
+		const auto manager = &owner->customEmojiManager();
+		const auto check = [=](not_null<DocumentData*> document) {
+			if (const auto sticker = document->sticker()) {
+				const auto setId = manager->coloredSetId();
+				const auto text = (setId == sticker->set.id)
+					? QString()
+					: sticker->alt;
+				if (text.isEmpty()) {
+					consumer.put_next({});
+				} else {
+					consumer.put_next({
+						.text = text,
+						.entities = { EntityInText(
+							EntityType::CustomEmoji,
+							0,
+							text.size(),
+							Data::SerializeCustomEmojiId(document)) },
+					});
+				}
+				return true;
+			}
+			return false;
+		};
+		auto lifetime = rpl::lifetime();
+		if (!check(document)) {
+			const auto manager = &owner->customEmojiManager();
+			const auto listener = new Listener(check);
+			lifetime.add([=] {
+				manager->unregisterListener(listener);
+				delete listener;
+			});
+			manager->resolve(id, listener);
+		}
+		return lifetime;
+	};
+}
+
+[[nodiscard]] rpl::producer<TextWithEntities> PeerCustomStatus(
+		not_null<PeerData*> peer) {
+	const auto user = peer->asUser();
+	if (!user) {
+		return rpl::single(TextWithEntities());
+	}
+	const auto owner = &user->owner();
+	return user->session().changes().peerFlagsValue(
+		user,
+		Data::PeerUpdate::Flag::EmojiStatus
+	) | rpl::map([=] {
+		const auto id = user->emojiStatusId();
+		return id
+			? ResolveIsCustom(owner, id)
+			: rpl::single(TextWithEntities());
+	}) | rpl::flatten_latest() | rpl::distinct_until_changed();
+}
+
 } // namespace
 
 class ContactStatus::BgButton final : public Ui::RippleButton {
@@ -78,7 +160,10 @@ class ContactStatus::Bar final : public Ui::RpWidget {
 public:
 	Bar(QWidget *parent, const QString &name);
 
-	void showState(State state);
+	void showState(
+		State state,
+		TextWithEntities status,
+		Fn<std::any(Fn<void()> customEmojiRepaint)> context);
 
 	[[nodiscard]] rpl::producer<> unarchiveClicks() const;
 	[[nodiscard]] rpl::producer<> addClicks() const;
@@ -87,10 +172,13 @@ public:
 	[[nodiscard]] rpl::producer<> reportClicks() const;
 	[[nodiscard]] rpl::producer<> closeClicks() const;
 	[[nodiscard]] rpl::producer<> requestInfoClicks() const;
+	[[nodiscard]] rpl::producer<> emojiStatusClicks() const;
 
 private:
 	int resizeGetHeight(int newWidth) override;
 
+	void emojiStatusRepaint();
+
 	QString _name;
 	object_ptr<Ui::FlatButton> _add;
 	object_ptr<Ui::FlatButton> _unarchive;
@@ -100,6 +188,10 @@ private:
 	object_ptr<Ui::IconButton> _close;
 	object_ptr<BgButton> _requestChatBg;
 	object_ptr<Ui::FlatLabel> _requestChatInfo;
+	object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _emojiStatusInfo;
+	object_ptr<Ui::PlainShadow> _emojiStatusShadow;
+	bool _emojiStatusRepaintScheduled = false;
+	rpl::event_stream<> _emojiStatusClicks;
 
 };
 
@@ -152,13 +244,30 @@ ContactStatus::Bar::Bar(
 , _close(this, st::historyReplyCancel)
 , _requestChatBg(this, st::historyContactStatusButton)
 , _requestChatInfo(
-		this,
-		QString(),
-		st::historyContactStatusLabel) {
+	this,
+	QString(),
+	st::historyContactStatusLabel)
+, _emojiStatusInfo(
+	this,
+	object_ptr<Ui::FlatLabel>(this, u""_q, st::historyEmojiStatusInfoLabel),
+	QMargins(
+		st::historyContactStatusMinSkip,
+		st::topBarArrowPadding.top(),
+		st::historyContactStatusMinSkip,
+		st::topBarArrowPadding.top()))
+, _emojiStatusShadow(this) {
 	_requestChatInfo->setAttribute(Qt::WA_TransparentForMouseEvents);
+	_emojiStatusInfo->paintRequest(
+	) | rpl::start_with_next([=, raw = _emojiStatusInfo.data()](QRect clip) {
+		_emojiStatusRepaintScheduled = false;
+		QPainter(raw).fillRect(clip, st::historyComposeButtonBg);
+	}, lifetime());
 }
 
-void ContactStatus::Bar::showState(State state) {
+void ContactStatus::Bar::showState(
+		State state,
+		TextWithEntities status,
+		Fn<std::any(Fn<void()> customEmojiRepaint)> context) {
 	using Type = State::Type;
 	const auto type = state.type;
 	_add->setVisible(type == Type::AddOrBlock || type == Type::Add);
@@ -172,6 +281,25 @@ void ContactStatus::Bar::showState(State state) {
 		|| type == Type::UnarchiveOrReport);
 	_requestChatInfo->setVisible(type == Type::RequestChatInfo);
 	_requestChatBg->setVisible(type == Type::RequestChatInfo);
+	const auto has = !status.empty();
+	_emojiStatusShadow->setVisible(
+		has && (type == Type::AddOrBlock || type == Type::UnarchiveOrBlock));
+	if (has) {
+		_emojiStatusInfo->entity()->setMarkedText(
+			tr::lng_new_contact_about_status(
+				tr::now,
+				lt_emoji,
+				status,
+				lt_link,
+				Ui::Text::Link(
+					tr::lng_new_contact_about_status_link(tr::now)),
+				Ui::Text::WithEntities),
+			context([=] { emojiStatusRepaint(); }));
+		_emojiStatusInfo->entity()->overrideLinkClickHandler([=] {
+			_emojiStatusClicks.fire({});
+		});
+	}
+	_emojiStatusInfo->setVisible(has);
 	_add->setText((type == Type::Add)
 		? tr::lng_new_contact_add_name(tr::now, lt_user, _name).toUpper()
 		: tr::lng_new_contact_add(tr::now).toUpper());
@@ -219,14 +347,19 @@ rpl::producer<> ContactStatus::Bar::requestInfoClicks() const {
 	return _requestChatBg->clicks() | rpl::to_empty;
 }
 
+rpl::producer<> ContactStatus::Bar::emojiStatusClicks() const {
+	return _emojiStatusClicks.events();
+}
+
 int ContactStatus::Bar::resizeGetHeight(int newWidth) {
 	_close->moveToRight(0, 0);
 
 	const auto closeWidth = _close->width();
+	const auto closeHeight = _close->height();
 	const auto available = newWidth - closeWidth;
 	const auto skip = st::historyContactStatusMinSkip;
 	if (available <= 2 * skip) {
-		return _close->height();
+		return closeHeight;
 	}
 	const auto buttonWidth = [&](const object_ptr<Ui::FlatButton> &button) {
 		return button->textWidth() + 2 * skip;
@@ -237,7 +370,7 @@ int ContactStatus::Bar::resizeGetHeight(int newWidth) {
 			const object_ptr<Ui::FlatButton> &button,
 			int buttonWidth,
 			int rightTextMargin = 0) {
-		button->setGeometry(accumulatedLeft, 0, buttonWidth, height());
+		button->setGeometry(accumulatedLeft, 0, buttonWidth, closeHeight);
 		button->setTextMargins({ 0, 0, rightTextMargin, 0 });
 		accumulatedLeft += buttonWidth;
 	};
@@ -287,7 +420,17 @@ int ContactStatus::Bar::resizeGetHeight(int newWidth) {
 		placeOne(_report);
 	}
 	if (_requestChatInfo->isHidden()) {
-		return _close->height();
+		_emojiStatusInfo->resizeToWidth(newWidth);
+		_emojiStatusInfo->move(0, _close->height());
+		_emojiStatusShadow->setGeometry(
+			0,
+			closeHeight,
+			newWidth,
+			st::lineWidth);
+		_emojiStatusShadow->move(0, _close->height());
+		return closeHeight + (_emojiStatusInfo->isHidden()
+			? 0
+			: _emojiStatusInfo->height());
 	}
 	const auto vskip = st::topBarArrowPadding.top();
 	_requestChatInfo->resizeToWidth(available - 2 * skip);
@@ -297,6 +440,14 @@ int ContactStatus::Bar::resizeGetHeight(int newWidth) {
 	return newHeight;
 }
 
+void ContactStatus::Bar::emojiStatusRepaint() {
+	if (_emojiStatusRepaintScheduled) {
+		return;
+	}
+	_emojiStatusRepaintScheduled = true;
+	_emojiStatusInfo->entity()->update();
+}
+
 ContactStatus::ContactStatus(
 	not_null<Window::SessionController*> window,
 	not_null<Ui::RpWidget*> parent,
@@ -393,15 +544,23 @@ void ContactStatus::setupState(not_null<PeerData*> peer) {
 		peer->session().api().requestPeerSettings(peer);
 	}
 
-	_bar.entity()->showState(State());
-	PeerState(
-		peer
-	) | rpl::start_with_next([=](State state) {
+	_context = [=](Fn<void()> customEmojiRepaint) {
+		return Core::MarkedTextContext{
+			.session = &peer->session(),
+			.customEmojiRepaint = customEmojiRepaint,
+		};
+	};
+	_bar.entity()->showState({}, {}, _context);
+	rpl::combine(
+		PeerState(peer),
+		PeerCustomStatus(peer)
+	) | rpl::start_with_next([=](State state, TextWithEntities status) {
 		_state = state;
+		_status = status;
 		if (state.type == State::Type::None) {
 			_bar.hide(anim::type::normal);
 		} else {
-			_bar.entity()->showState(state);
+			_bar.entity()->showState(state, std::move(status), _context);
 			_bar.show(anim::type::normal);
 		}
 	}, _bar.lifetime());
@@ -417,6 +576,7 @@ void ContactStatus::setupHandlers(not_null<PeerData*> peer) {
 	setupReportHandler(peer);
 	setupCloseHandler(peer);
 	setupRequestInfoHandler(peer);
+	setupEmojiStatusHandler(peer);
 }
 
 void ContactStatus::setupAddHandler(not_null<UserData*> user) {
@@ -583,12 +743,19 @@ void ContactStatus::setupRequestInfoHandler(not_null<PeerData*> peer) {
 	}, _bar.lifetime());
 }
 
+void ContactStatus::setupEmojiStatusHandler(not_null<PeerData*> peer) {
+	_bar.entity()->emojiStatusClicks(
+	) | rpl::start_with_next([=] {
+		Settings::ShowEmojiStatusPremium(_controller, peer);
+	}, _bar.lifetime());
+}
+
 void ContactStatus::show() {
 	const auto visible = (_state.type != State::Type::None);
 	if (!_shown) {
 		_shown = true;
 		if (visible) {
-			_bar.entity()->showState(_state);
+			_bar.entity()->showState(_state, _status, _context);
 		}
 	}
 	_bar.toggle(visible, anim::type::instant);
diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.h b/Telegram/SourceFiles/history/view/history_view_contact_status.h
index 56b696fcf..9dbab24e4 100644
--- a/Telegram/SourceFiles/history/view/history_view_contact_status.h
+++ b/Telegram/SourceFiles/history/view/history_view_contact_status.h
@@ -72,11 +72,14 @@ private:
 	void setupReportHandler(not_null<PeerData*> peer);
 	void setupCloseHandler(not_null<PeerData*> peer);
 	void setupRequestInfoHandler(not_null<PeerData*> peer);
+	void setupEmojiStatusHandler(not_null<PeerData*> peer);
 
 	static rpl::producer<State> PeerState(not_null<PeerData*> peer);
 
 	const not_null<Window::SessionController*> _controller;
 	State _state;
+	TextWithEntities _status;
+	Fn<std::any(Fn<void()> customEmojiRepaint)> _context;
 	Ui::SlideWrap<Bar> _bar;
 	Ui::PlainShadow _shadow;
 	bool _shown = false;
diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
index 931b3da0e..8643f5720 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
@@ -354,7 +354,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
 			std::move(linkText),
 			QString());
 		const auto controller = _controller->parentController();
-		link->setClickHandlerFilter([=, peer = _peer](auto&&...) {
+		link->overrideLinkClickHandler([=, peer = _peer] {
 			const auto link = peer->session().createInternalLinkFull(
 				peer->userName());
 			if (!link.isEmpty()) {
@@ -363,7 +363,6 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
 					Window::Show(controller).toastParent(),
 					tr::lng_username_copied(tr::now));
 			}
-			return false;
 		});
 
 		if (const auto channel = _peer->asChannel()) {
diff --git a/Telegram/SourceFiles/intro/intro_widget.cpp b/Telegram/SourceFiles/intro/intro_widget.cpp
index f3e80a9fc..24aab653e 100644
--- a/Telegram/SourceFiles/intro/intro_widget.cpp
+++ b/Telegram/SourceFiles/intro/intro_widget.cpp
@@ -460,13 +460,8 @@ void Widget::showTerms() {
 				Ui::Text::WithEntities),
 			st::introTermsLabel);
 		_terms.create(this, std::move(entity));
-		_terms->entity()->setClickHandlerFilter([=](
-				const ClickHandlerPtr &handler,
-				Qt::MouseButton button) {
-			if (button == Qt::LeftButton) {
-				showTerms(nullptr);
-			}
-			return false;
+		_terms->entity()->overrideLinkClickHandler([=] {
+			showTerms(nullptr);
 		});
 		updateControlsGeometry();
 		_terms->hide(anim::type::instant);
diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp
index f59e1d232..874efae81 100644
--- a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp
+++ b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp
@@ -154,10 +154,7 @@ void VerifyBox::setupControls(
 		) | rpl::start_with_next([=] {
 			_content->resizeToWidth(st::boxWidth);
 		}, _content->lifetime());
-		label->setClickHandlerFilter([=](auto&&...) {
-			resend();
-			return false;
-		});
+		label->overrideLinkClickHandler(resend);
 	}
 	std::move(
 		error
diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp
index 3bc4a05aa..4a9ab7e4c 100644
--- a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp
+++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp
@@ -359,10 +359,9 @@ void FormSummary::setupPrices(not_null<VerticalLayout*> layout) {
 		const auto text = formatAmount(_invoice.tipsSelected);
 		const auto label = addRow(
 			tr::lng_payments_tips_label(tr::now),
-			Ui::Text::Link(text, "internal:edit_tips"));
-		label->setClickHandlerFilter([=](auto&&...) {
+			Ui::Text::Link(text));
+		label->overrideLinkClickHandler([=] {
 			_delegate->panelChooseTips();
-			return false;
 		});
 		setupSuggestedTips(layout);
 	}
diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp
index f4a73cfc3..0db0b5300 100644
--- a/Telegram/SourceFiles/settings/settings_main.cpp
+++ b/Telegram/SourceFiles/settings/settings_main.cpp
@@ -192,7 +192,7 @@ void Cover::initViewers() {
 		refreshUsernameGeometry(width());
 	}, lifetime());
 
-	_username->setClickHandlerFilter([=](auto&&...) {
+	_username->overrideLinkClickHandler([=] {
 		const auto username = _user->userName();
 		if (username.isEmpty()) {
 			_controller->show(Box<UsernameBox>(&_user->session()));
@@ -203,7 +203,6 @@ void Cover::initViewers() {
 				Window::Show(_controller).toastParent(),
 				tr::lng_username_copied(tr::now));
 		}
-		return false;
 	});
 }
 
diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
index 683d8fbe1..d352b3fbd 100644
--- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
+++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp
@@ -498,17 +498,11 @@ auto PhoneNumberPrivacyController::warning() const
 
 void PhoneNumberPrivacyController::prepareWarningLabel(
 		not_null<Ui::FlatLabel*> warning) const {
-	warning->setClickHandlerFilter([=](
-			const ClickHandlerPtr &link,
-			Qt::MouseButton button) {
-		if (button == Qt::LeftButton) {
-			QGuiApplication::clipboard()->setText(PublicLinkByPhone(
-				_controller->session().user()));
-			_controller->window().showToast(
-				tr::lng_username_copied(tr::now));
-			return false;
-		}
-		return true;
+	warning->overrideLinkClickHandler([=] {
+		QGuiApplication::clipboard()->setText(PublicLinkByPhone(
+			_controller->session().user()));
+		_controller->window().showToast(
+			tr::lng_username_copied(tr::now));
 	});
 }
 
diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style
index 50249d8e3..d2f7208a5 100644
--- a/Telegram/SourceFiles/ui/chat/chat.style
+++ b/Telegram/SourceFiles/ui/chat/chat.style
@@ -305,6 +305,10 @@ historyContactStatusBlock: FlatButton(historyContactStatusButton) {
 historyContactStatusLabel: FlatLabel(defaultFlatLabel) {
 	minWidth: 240px;
 }
+historyEmojiStatusInfoLabel: FlatLabel(historyContactStatusLabel) {
+	align: align(top);
+	textFg: windowSubTextFg;
+}
 historyContactStatusMinSkip: 16px;
 
 historySendIcon: icon {{ "chat/input_send", historySendIconFg }};
diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index 2e63c6103..4ec399f16 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit 2e63c6103e3b23bfcd65dcb8afb19c020511b168
+Subproject commit 4ec399f169e9308cd5da194b6fa2104578c39e45