From b32e32274923a94742a9926ef38785f746d41405 Mon Sep 17 00:00:00 2001
From: sk <sk22@mailbox.org>
Date: Fri, 23 Dec 2022 17:51:06 +0100
Subject: [PATCH] implement long-click to copy links

closes sk22#84
---
 .../displayitems/FooterStatusDisplayItem.java  |  2 +-
 .../ui/text/ClickableLinksDelegate.java        | 18 +++++++++++++++---
 .../android/ui/text/HtmlParser.java            |  6 +++---
 .../joinmastodon/android/ui/text/LinkSpan.java |  8 +++++++-
 4 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
index 25cc444a29..a6a5e79d7c 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
@@ -150,7 +150,7 @@ private boolean onButtonTouch(View v, MotionEvent event){
 				v.removeCallbacks(longClickRunnable);
 				v.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
 				if (disabled) return true;
-				if (action == MotionEvent.ACTION_UP && eventDuration < ViewConfiguration.getLongPressTimeout()) v.performClick();
+				if (action == MotionEvent.ACTION_UP && eventDuration <= ViewConfiguration.getLongPressTimeout()) v.performClick();
 				else v.startAnimation(opacityIn);
 			} else if (action == MotionEvent.ACTION_DOWN) {
 				touchingView = v;
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java
index 482a7eacf5..e3a1bfc61f 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java
@@ -10,6 +10,8 @@
 import android.text.Spanned;
 import android.view.MotionEvent;
 import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewConfiguration;
 import android.widget.TextView;
 
 import me.grishka.appkit.utils.V;
@@ -20,7 +22,11 @@ public class ClickableLinksDelegate {
 	private Path hlPath;
 	private LinkSpan selectedSpan;
 	private TextView view;
-	
+
+	private final Runnable longClickRunnable = () -> {
+		if (selectedSpan != null) selectedSpan.onLongClick(view.getContext());
+	};
+
 	public ClickableLinksDelegate(TextView view) {
 		this.view=view;
 		hlPaint=new Paint();
@@ -30,6 +36,7 @@ public ClickableLinksDelegate(TextView view) {
 	}
 
 	public boolean onTouch(MotionEvent event) {
+		long eventDuration = event.getEventTime() - event.getDownTime();
 		if(event.getAction()==MotionEvent.ACTION_DOWN){
 			int line=-1;
 			Rect rect=new Rect();
@@ -63,6 +70,7 @@ public boolean onTouch(MotionEvent event) {
 							}
 							hlPath=new Path();
 							selectedSpan=span;
+							view.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
 							hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000);
 							//l.getSelectionPath(start, end, hlPath);
 							for(int j=lstart;j<=lend;j++){
@@ -90,8 +98,11 @@ public boolean onTouch(MotionEvent event) {
 			}
 		}
 		if(event.getAction()==MotionEvent.ACTION_UP && selectedSpan!=null){
-			view.playSoundEffect(SoundEffectConstants.CLICK);
-			selectedSpan.onClick(view.getContext());
+			if (eventDuration <= ViewConfiguration.getLongPressTimeout()) {
+				view.playSoundEffect(SoundEffectConstants.CLICK);
+				selectedSpan.onClick(view.getContext());
+			}
+			view.removeCallbacks(longClickRunnable);
 			hlPath=null;
 			selectedSpan=null;
 			view.invalidate();
@@ -100,6 +111,7 @@ public boolean onTouch(MotionEvent event) {
 		if(event.getAction()==MotionEvent.ACTION_CANCEL){
 			hlPath=null;
 			selectedSpan=null;
+			view.removeCallbacks(longClickRunnable);
 			view.invalidate();
 			return false;
 		}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
index e5841f8d53..2e60018321 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
@@ -117,8 +117,8 @@ public void head(@NonNull Node node, int depth){
 						case "a" -> {
 							String href=el.attr("href");
 							LinkSpan.Type linkType;
+							String text=el.text();
 							if(el.hasClass("hashtag")){
-								String text=el.text();
 								if(text.startsWith("#")){
 									linkType=LinkSpan.Type.HASHTAG;
 									href=text.substring(1);
@@ -136,7 +136,7 @@ public void head(@NonNull Node node, int depth){
 							}else{
 								linkType=LinkSpan.Type.URL;
 							}
-							openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID), ssb.length(), el));
+							openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID, text), ssb.length(), el));
 						}
 						case "br" -> ssb.append('\n');
 						case "span" -> {
@@ -260,7 +260,7 @@ public static CharSequence parseLinks(String text){
 			String url=matcher.group(3);
 			if(TextUtils.isEmpty(matcher.group(4)))
 				url="http://"+url;
-			ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0);
+			ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null, url), matcher.start(3), matcher.end(3), 0);
 		}while(matcher.find()); // Find more URLs
 		return ssb;
 	}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java
index 34eff30739..9cc570efe6 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java
@@ -13,12 +13,14 @@ public class LinkSpan extends CharacterStyle {
 	private String link;
 	private Type type;
 	private String accountID;
+	private String text;
 
-	public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID){
+	public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, String text){
 		this.listener=listener;
 		this.link=link;
 		this.type=type;
 		this.accountID=accountID;
+		this.text=text;
 	}
 
 	public int getColor(){
@@ -38,6 +40,10 @@ public void onClick(Context context){
 		}
 	}
 
+	public void onLongClick(Context context) {
+		UiUtils.copyText(context, getType() == Type.URL ? link : text);
+	}
+
 	public String getLink(){
 		return link;
 	}