From ae7e5be5cdcde1b09160c8beb98727251271186a Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Sat, 30 Dec 2017 11:15:42 +0300
Subject: [PATCH] Add fast reply button in groups.

---
 Telegram/Resources/langs/lang.strings         |   1 +
 .../SourceFiles/history/history_message.cpp   | 106 +++++++++++++++---
 .../SourceFiles/history/history_message.h     |  16 ++-
 Telegram/SourceFiles/mainwidget.cpp           |   8 ++
 Telegram/SourceFiles/mainwidget.h             |   1 +
 5 files changed, 110 insertions(+), 22 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 9fa210bf5..e4f3af2ba 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -863,6 +863,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 "lng_edited" = "edited";
 "lng_edited_date" = "Edited: {date}";
 "lng_admin_badge" = "admin";
+"lng_fast_reply" = "Reply";
 "lng_cancel_edit_post_sure" = "Cancel editing?";
 "lng_cancel_edit_post_yes" = "Yes";
 "lng_cancel_edit_post_no" = "No";
diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp
index ad738b905..3ea6d2d3e 100644
--- a/Telegram/SourceFiles/history/history_message.cpp
+++ b/Telegram/SourceFiles/history/history_message.cpp
@@ -143,6 +143,10 @@ QString AdminBadgeText() {
 	return lang(lng_admin_badge);
 }
 
+QString FastReplyText() {
+	return lang(lng_fast_reply);
+}
+
 style::color FromNameFg(not_null<PeerData*> peer, bool selected) {
 	if (selected) {
 		const style::color colors[] = {
@@ -1151,10 +1155,16 @@ void HistoryMessage::initDimensions() {
 				if (via && !forwarded) {
 					namew += st::msgServiceFont->spacew + via->maxWidth;
 				}
+				const auto replyWidth = hasFastReply()
+					? st::msgFont->width(FastReplyText())
+					: 0;
 				if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
-					auto badgeWidth = st::msgServiceFont->width(
+					const auto badgeWidth = st::msgFont->width(
 						AdminBadgeText());
-					namew += st::msgPadding.right() + badgeWidth;
+					namew += st::msgPadding.right()
+						+ std::max(badgeWidth, replyWidth);
+				} else if (replyWidth) {
+					namew += st::msgPadding.right() + replyWidth;
 				}
 				accumulate_max(_maxw, namew);
 			} else if (via && !forwarded) {
@@ -1216,6 +1226,15 @@ bool HistoryMessage::hasFromName() const {
 		&& (!history()->peer->isUser() || history()->peer->isSelf());
 }
 
+bool HistoryMessage::hasFastReply() const {
+	return !hasOutLayout()
+		&& (history()->peer->isChat() || history()->peer->isMegagroup());
+}
+
+bool HistoryMessage::displayFastReply() const {
+	return hasFastReply() && history()->peer->canWrite();
+}
+
 QRect HistoryMessage::countGeometry() const {
 	auto maxwidth = qMin(st::msgMaxWidth, _maxw);
 	if (_media && _media->currentWidth() < maxwidth) {
@@ -1252,10 +1271,14 @@ QRect HistoryMessage::countGeometry() const {
 }
 
 void HistoryMessage::fromNameUpdated(int32 width) const {
+	const auto replyWidth = hasFastReply()
+		? st::msgFont->width(FastReplyText())
+		: 0;
 	if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
-		auto badgeWidth = st::msgServiceFont->width(
-			AdminBadgeText());
-		width -= st::msgPadding.right() + badgeWidth;
+		const auto badgeWidth = st::msgFont->width(AdminBadgeText());
+		width -= st::msgPadding.right() + std::max(badgeWidth, replyWidth);
+	} else if (replyWidth) {
+		width -= st::msgPadding.right() + replyWidth;
 	}
 	_fromNameVersion = displayFrom()->nameVersion;
 	if (!Has<HistoryMessageForwarded>()) {
@@ -1879,18 +1902,28 @@ void HistoryMessage::drawRightAction(Painter &p, int left, int top, int outerWid
 	}
 }
 
-void HistoryMessage::paintFromName(Painter &p, QRect &trect, bool selected) const {
+void HistoryMessage::paintFromName(
+		Painter &p,
+		QRect &trect,
+		bool selected) const {
 	if (displayFromName()) {
-		auto badgeWidth = [&] {
+		const auto badgeWidth = [&] {
 			if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
-				return st::msgServiceFont->width(AdminBadgeText());
+				return st::msgFont->width(AdminBadgeText());
 			}
 			return 0;
 		}();
+		const auto replyWidth = [&] {
+			if (App::hoveredItem() == this && displayFastReply()) {
+				return st::msgFont->width(FastReplyText());
+			}
+			return 0;
+		}();
+		const auto rightWidth = replyWidth ? replyWidth : badgeWidth;
 		auto availableLeft = trect.left();
 		auto availableWidth = trect.width();
-		if (badgeWidth) {
-			availableWidth -= st::msgPadding.right() + badgeWidth;
+		if (rightWidth) {
+			availableWidth -= st::msgPadding.right() + rightWidth;
 		}
 
 		p.setFont(st::msgNameFont);
@@ -1914,13 +1947,15 @@ void HistoryMessage::paintFromName(Painter &p, QRect &trect, bool selected) cons
 			availableLeft += skipWidth;
 			availableWidth -= skipWidth;
 		}
-		if (badgeWidth) {
+		if (rightWidth) {
 			p.setPen(selected ? st::msgInDateFgSelected : st::msgInDateFg);
-			p.setFont(st::msgFont);
+			p.setFont(ClickHandler::showAsActive(_fastReplyLink)
+				? st::msgFont->underline()
+				: st::msgFont);
 			p.drawText(
-				trect.left() + trect.width() - badgeWidth,
+				trect.left() + trect.width() - rightWidth,
 				trect.top() + st::msgFont->ascent,
-				AdminBadgeText());
+				replyWidth ? FastReplyText() : AdminBadgeText());
 		}
 		trect.setY(trect.y() + st::msgNameFont->height);
 	}
@@ -2268,6 +2303,20 @@ ClickHandlerPtr HistoryMessage::rightActionLink() const {
 	return _rightActionLink;
 }
 
+ClickHandlerPtr HistoryMessage::fastReplyLink() const {
+	if (!_fastReplyLink) {
+		const auto itemId = fullId();
+		_fastReplyLink = std::make_shared<LambdaClickHandler>([=] {
+			if (const auto item = App::histItemById(itemId)) {
+				if (const auto main = App::main()) {
+					main->replyToItem(item);
+				}
+			}
+		});
+	}
+	return _fastReplyLink;
+}
+
 // Forward to _media.
 void HistoryMessage::updatePressed(QPoint point) {
 	if (!_media) return;
@@ -2324,15 +2373,40 @@ bool HistoryMessage::getStateFromName(
 		QRect &trect,
 		not_null<HistoryTextState*> outResult) const {
 	if (displayFromName()) {
+		const auto replyWidth = [&] {
+			if (App::hoveredItem() == this && displayFastReply()) {
+				return st::msgFont->width(FastReplyText());
+			}
+			return 0;
+		}();
+		if (replyWidth
+			&& point.x() >= trect.left() + trect.width() - replyWidth
+			&& point.x() < trect.left() + trect.width() + st::msgPadding.right()
+			&& point.y() >= trect.top() - st::msgPadding.top()
+			&& point.y() < trect.top() + st::msgServiceFont->height) {
+			outResult->link = fastReplyLink();
+			return true;
+		}
 		if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
+			auto availableLeft = trect.left();
+			auto availableWidth = trect.width();
+			if (replyWidth) {
+				availableWidth -= st::msgPadding.right() + replyWidth;
+			}
 			auto user = displayFrom();
-			if (point.x() >= trect.left() && point.x() < trect.left() + trect.width() && point.x() < trect.left() + user->nameText.maxWidth()) {
+			if (point.x() >= availableLeft
+				&& point.x() < availableLeft + availableWidth
+				&& point.x() < availableLeft + user->nameText.maxWidth()) {
 				outResult->link = user->openLink();
 				return true;
 			}
 			auto forwarded = Get<HistoryMessageForwarded>();
 			auto via = Get<HistoryMessageVia>();
-			if (via && !forwarded && point.x() >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && point.x() < trect.left() + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->width) {
+			if (via
+				&& !forwarded
+				&& point.x() >= availableLeft + author()->nameText.maxWidth() + st::msgServiceFont->spacew
+				&& point.x() < availableLeft + availableWidth
+				&& point.x() < availableLeft + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->width) {
 				outResult->link = via->link;
 				return true;
 			}
diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h
index 3ab5ca561..765887ab5 100644
--- a/Telegram/SourceFiles/history/history_message.h
+++ b/Telegram/SourceFiles/history/history_message.h
@@ -169,6 +169,8 @@ public:
 		if (isAttachedToPrevious()) return false;
 		return true;
 	}
+	bool hasFastReply() const;
+	bool displayFastReply() const;
 	bool displayForwardedFrom() const;
 	bool uploading() const;
 	bool displayRightAction() const override;
@@ -360,17 +362,19 @@ private:
 	bool displayFastShare() const;
 	bool displayGoToOriginal() const;
 
-	QString _timeText;
-	int _timeWidth = 0;
-
-	mutable ClickHandlerPtr _rightActionLink;
-	mutable int32 _fromNameVersion = 0;
-
 	struct CreateConfig;
 	void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup);
 	void createComponents(const CreateConfig &config);
 
 	void updateMediaInBubbleState();
 	void updateAdminBadgeState();
+	ClickHandlerPtr fastReplyLink() const;
+
+	QString _timeText;
+	int _timeWidth = 0;
+
+	mutable ClickHandlerPtr _rightActionLink;
+	mutable ClickHandlerPtr _fastReplyLink;
+	mutable int32 _fromNameVersion = 0;
 
 };
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 4f45e5a65..e2b35a091 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -683,6 +683,14 @@ bool MainWidget::shareUrl(
 	return true;
 }
 
+void MainWidget::replyToItem(not_null<HistoryItem*> item) {
+	if (_history->peer() == item->history()->peer
+		|| _history->peer() == item->history()->peer->migrateTo()) {
+		App::contextItem(item);
+		_history->onReplyToMessage();
+	}
+}
+
 bool MainWidget::onInlineSwitchChosen(const PeerId &peer, const QString &botAndQuery) {
 	PeerData *p = App::peer(peer);
 	if (!peer || !p->canWrite()) {
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index a49b411bb..81e120621 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -191,6 +191,7 @@ public:
 		not_null<PeerData*> peer,
 		const QString &url,
 		const QString &text);
+	void replyToItem(not_null<HistoryItem*> item);
 	bool onInlineSwitchChosen(const PeerId &peer, const QString &botAndQuery);
 	bool onSendPaths(const PeerId &peer);
 	void onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data);