From 09e6077e974cbf47c299f7a448b6178a0eb83d0c Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 27 Jun 2024 16:55:19 +0400
Subject: [PATCH] Suggest global search of cashtags.

---
 .../dialogs/dialogs_inner_widget.cpp          |  7 ++--
 .../dialogs/dialogs_inner_widget.h            |  3 +-
 .../SourceFiles/dialogs/dialogs_widget.cpp    | 20 ++++++-----
 Telegram/SourceFiles/dialogs/dialogs_widget.h |  3 +-
 .../SourceFiles/dialogs/ui/chat_search_in.cpp | 36 ++++++++++++-------
 .../SourceFiles/dialogs/ui/chat_search_in.h   | 11 ++++--
 6 files changed, 52 insertions(+), 28 deletions(-)

diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index e10544c6d..fe5651774 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -2655,7 +2655,7 @@ void InnerWidget::applySearchState(SearchState state) {
 		onHashtagFilterUpdate(QStringView());
 	}
 	_searchState = std::move(state);
-	_searchingHashtag = IsHashtagSearchQuery(_searchState.query);
+	_searchHashOrCashtag = IsHashOrCashtagSearchQuery(_searchState.query);
 
 	updateSearchIn();
 	moveSearchIn();
@@ -3332,7 +3332,8 @@ auto InnerWidget::searchTagsChanges() const
 }
 
 void InnerWidget::updateSearchIn() {
-	if (!_searchState.inChat && !_searchingHashtag) {
+	if (!_searchState.inChat
+		&& _searchHashOrCashtag == HashOrCashtag::None) {
 		_searchIn = nullptr;
 		return;
 	} else if (!_searchIn) {
@@ -3381,7 +3382,7 @@ void InnerWidget::updateSearchIn() {
 		? Ui::MakeUserpicThumbnail(sublist->peer())
 		: nullptr;
 	const auto myIcon = Ui::MakeIconThumbnail(st::menuIconChats);
-	const auto publicIcon = _searchingHashtag
+	const auto publicIcon = (_searchHashOrCashtag != HashOrCashtag::None)
 		? Ui::MakeIconThumbnail(st::menuIconChannel)
 		: nullptr;
 	const auto peerTabType = (peer && peer->isBroadcast())
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index 3adad0cf4..033418af1 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -62,6 +62,7 @@ class IndexedList;
 class SearchTags;
 class SearchEmpty;
 class ChatSearchIn;
+enum class HashOrCashtag : uchar;
 
 struct ChosenRow {
 	Key key;
@@ -514,7 +515,7 @@ private:
 	Ui::DraggingScrollManager _draggingScroll;
 
 	SearchState _searchState;
-	bool _searchingHashtag = false;
+	HashOrCashtag _searchHashOrCashtag = {};
 	History *_searchInMigrated = nullptr;
 	PeerData *_searchFromShown = nullptr;
 	Ui::Text::String _searchFromUserText;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index 29e0400d7..913425a79 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -2125,14 +2125,14 @@ bool Widget::searchForPeersRequired(const QString &query) const {
 	return _searchState.filterChatsList()
 		&& !_openedForum
 		&& !query.isEmpty()
-		&& !IsHashtagSearchQuery(query);
+		&& (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None);
 }
 
 bool Widget::searchForTopicsRequired(const QString &query) const {
 	return _searchState.filterChatsList()
 		&& _openedForum
 		&& !query.isEmpty()
-		&& !IsHashtagSearchQuery(query)
+		&& (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None)
 		&& !_openedForum->topicsList()->loaded();
 }
 
@@ -2654,16 +2654,19 @@ void Widget::updateCancelSearch() {
 QString Widget::validateSearchQuery() {
 	const auto query = currentSearchQuery();
 	if (_searchState.tab == ChatSearchTab::PublicPosts) {
-		_searchingHashtag = true;
+		if (_searchHashOrCashtag == HashOrCashtag::None) {
+			_searchHashOrCashtag = HashOrCashtag::Hashtag;
+		}
 		const auto fixed = FixHashtagSearchQuery(
 			query,
-			currentSearchQueryCursorPosition());
+			currentSearchQueryCursorPosition(),
+			_searchHashOrCashtag);
 		if (fixed.text != query) {
 			setSearchQuery(fixed.text, fixed.cursorPosition);
 		}
 		return fixed.text;
 	} else {
-		_searchingHashtag = IsHashtagSearchQuery(query);
+		_searchHashOrCashtag = IsHashOrCashtagSearchQuery(query);
 	}
 	return query;
 }
@@ -2860,11 +2863,12 @@ bool Widget::applySearchState(SearchState state) {
 		state.fromPeer = nullptr;
 	}
 	if (state.tab == ChatSearchTab::PublicPosts
-		&& !IsHashtagSearchQuery(state.query)) {
+		&& IsHashOrCashtagSearchQuery(state.query) == HashOrCashtag::None) {
 		state.tab = (_openedForum && !state.inChat)
 			? ChatSearchTab::ThisPeer
 			: ChatSearchTab::MyMessages;
-	} else if (!state.inChat && !_searchingHashtag) {
+	} else if (!state.inChat
+		&& _searchHashOrCashtag == HashOrCashtag::None) {
 		state.tab = (forum || _openedForum)
 			? ChatSearchTab::ThisPeer
 			: ChatSearchTab::MyMessages;
@@ -2904,7 +2908,7 @@ bool Widget::applySearchState(SearchState state) {
 			&& !state.inChat
 			&& !_openedForum)
 		|| (state.tab == ChatSearchTab::PublicPosts
-			&& !_searchingHashtag)) {
+			&& _searchHashOrCashtag == HashOrCashtag::None)) {
 		state.tab = state.inChat.topic()
 			? ChatSearchTab::ThisTopic
 			: (state.inChat.owningHistory() || state.inChat.sublist())
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h
index 46e5b55e0..37fdcaae2 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h
@@ -79,6 +79,7 @@ enum class SearchRequestDelay : uchar;
 class Suggestions;
 class ChatSearchIn;
 enum class ChatSearchTab : uchar;
+enum class HashOrCashtag : uchar;
 
 class Widget final : public Window::AbstractSectionWidget {
 public:
@@ -314,7 +315,7 @@ private:
 	object_ptr<Ui::JumpDownButton> _scrollToTop;
 	bool _scrollToTopIsShown = false;
 	bool _forumSearchRequested = false;
-	bool _searchingHashtag = false;
+	HashOrCashtag _searchHashOrCashtag = {};
 
 	Data::Folder *_openedFolder = nullptr;
 	Data::Forum *_openedForum = nullptr;
diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp
index 0524b3a63..310f687ba 100644
--- a/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp
+++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.cpp
@@ -199,12 +199,14 @@ void Action::handleKeyPress(not_null<QKeyEvent*> e) {
 
 FixedHashtagSearchQuery FixHashtagSearchQuery(
 		const QString &query,
-		int cursorPosition) {
+		int cursorPosition,
+		HashOrCashtag tag) {
 	const auto trimmed = query.trimmed();
 	const auto hash = int(trimmed.isEmpty()
 		? query.size()
 		: query.indexOf(trimmed));
 	const auto start = std::min(cursorPosition, hash);
+	const auto first = QChar(tag == HashOrCashtag::Cashtag ? '$' : '#');
 	auto result = query.mid(0, start);
 	for (const auto &ch : query.mid(start)) {
 		if (ch.isSpace()) {
@@ -213,33 +215,41 @@ FixedHashtagSearchQuery FixHashtagSearchQuery(
 			}
 			continue;
 		} else if (result.size() == start) {
-			result += '#';
-			if (ch != '#') {
+			result += first;
+			if (ch != first) {
 				++cursorPosition;
 			}
 		}
-		if (ch != '#') {
+		if (ch != first) {
 			result += ch;
 		}
 	}
 	if (result.size() == start) {
-		result += '#';
+		result += first;
 		++cursorPosition;
 	}
 	return { result, cursorPosition };
 }
 
-bool IsHashtagSearchQuery(const QString &query) {
+HashOrCashtag IsHashOrCashtagSearchQuery(const QString &query) {
 	const auto trimmed = query.trimmed();
-	if (trimmed.isEmpty() || trimmed[0] != '#') {
-		return false;
-	}
-	for (const auto &ch : trimmed) {
-		if (ch.isSpace()) {
-			return false;
+	const auto first = trimmed.isEmpty() ? QChar() : trimmed[0];
+	if (first == '#') {
+		for (const auto &ch : trimmed) {
+			if (ch.isSpace()) {
+				return HashOrCashtag::None;
+			}
 		}
+		return HashOrCashtag::Hashtag;
+	} else if (first == '$') {
+		for (const auto &ch : trimmed.midRef(1)) {
+			if (ch < 'A' || ch > 'Z') {
+				return HashOrCashtag::None;
+			}
+		}
+		return HashOrCashtag::Cashtag;
 	}
-	return true;
+	return HashOrCashtag::None;
 }
 
 void ChatSearchIn::Section::update() {
diff --git a/Telegram/SourceFiles/dialogs/ui/chat_search_in.h b/Telegram/SourceFiles/dialogs/ui/chat_search_in.h
index 5e99f2d05..b5b742616 100644
--- a/Telegram/SourceFiles/dialogs/ui/chat_search_in.h
+++ b/Telegram/SourceFiles/dialogs/ui/chat_search_in.h
@@ -87,14 +87,21 @@ private:
 
 };
 
+enum class HashOrCashtag : uchar {
+	None,
+	Hashtag,
+	Cashtag,
+};
+
 struct FixedHashtagSearchQuery {
 	QString text;
 	int cursorPosition = 0;
 };
 [[nodiscard]] FixedHashtagSearchQuery FixHashtagSearchQuery(
 	const QString &query,
-	int cursorPosition);
+	int cursorPosition,
+	HashOrCashtag tag);
 
-[[nodiscard]] bool IsHashtagSearchQuery(const QString &query);
+[[nodiscard]] HashOrCashtag IsHashOrCashtagSearchQuery(const QString &query);
 
 } // namespace Dialogs