Allow text selection with multiple links in message

Mishiranu created

Change summary

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java         | 51 
src/main/java/eu/siacs/conversations/ui/widget/ClickableMovementMethod.java | 42 
2 files changed, 56 insertions(+), 37 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -15,6 +15,7 @@ import android.os.Build;
 import android.support.v4.content.FileProvider;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.RelativeSizeSpan;
@@ -51,6 +52,7 @@ import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Message.FileParams;
 import eu.siacs.conversations.entities.Transferable;
 import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.ui.widget.ClickableMovementMethod;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.GeoHelper;
 import eu.siacs.conversations.utils.UIHelper;
@@ -314,7 +316,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 			if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) {
 				body = body.substring(0, Config.MAX_DISPLAY_MESSAGE_CHARS)+"\u2026";
 			}
-			final SpannableString formattedBody = new SpannableString(body);
+			Spannable formattedBody = new SpannableString(body);
 			int i = body.indexOf(Message.MERGE_SEPARATOR);
 			while(i >= 0) {
 				final int end = i + Message.MERGE_SEPARATOR.length();
@@ -323,12 +325,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 			}
 			if (message.getType() != Message.TYPE_PRIVATE) {
 				if (message.hasMeCommand()) {
-					final Spannable span = new SpannableString(formattedBody);
-					span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
+					formattedBody.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
 							Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-					viewHolder.messageBody.setText(span);
-				} else {
-					viewHolder.messageBody.setText(formattedBody);
 				}
 			} else {
 				String privateMarker;
@@ -344,46 +342,25 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 					}
 					privateMarker = activity.getString(R.string.private_message_to, to);
 				}
-				final Spannable span = new SpannableString(privateMarker + " "
-						+ formattedBody);
-				span.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground,false)), 0, privateMarker
+				formattedBody = new SpannableStringBuilder().append(privateMarker).append(' ').append(formattedBody);
+				formattedBody.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground,false)), 0, privateMarker
 						.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-				span.setSpan(new StyleSpan(Typeface.BOLD), 0,
+				formattedBody.setSpan(new StyleSpan(Typeface.BOLD), 0,
 						privateMarker.length(),
 						Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 				if (message.hasMeCommand()) {
-					span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
+					formattedBody.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
 							privateMarker.length() + 1 + nick.length(),
 							Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 				}
-				viewHolder.messageBody.setText(span);
-			}
-			int urlCount = 0;
-			final Matcher matcher = Patterns.WEB_URL.matcher(body);
-			int beginWebURL = Integer.MAX_VALUE;
-			int endWebURL = 0;
-			while (matcher.find()) {
-				MatchResult result = matcher.toMatchResult();
-				beginWebURL = result.start();
-				endWebURL = result.end();
-				urlCount++;
-			}
-			final Matcher geoMatcher = GeoHelper.GEO_URI.matcher(body);
-			while (geoMatcher.find()) {
-				urlCount++;
-			}
-			final Matcher xmppMatcher = XMPP_PATTERN.matcher(body);
-			while (xmppMatcher.find()) {
-				MatchResult result = xmppMatcher.toMatchResult();
-				if (beginWebURL < result.start() || endWebURL > result.end()) {
-					urlCount++;
-				}
 			}
-			viewHolder.messageBody.setTextIsSelectable(urlCount <= 1);
+			Linkify.addLinks(formattedBody, Linkify.WEB_URLS);
+			Linkify.addLinks(formattedBody, XMPP_PATTERN, "xmpp");
+			Linkify.addLinks(formattedBody, GeoHelper.GEO_URI, "geo");
 			viewHolder.messageBody.setAutoLinkMask(0);
-			Linkify.addLinks(viewHolder.messageBody, Linkify.WEB_URLS);
-			Linkify.addLinks(viewHolder.messageBody, XMPP_PATTERN, "xmpp");
-			Linkify.addLinks(viewHolder.messageBody, GeoHelper.GEO_URI, "geo");
+			viewHolder.messageBody.setText(formattedBody);
+			viewHolder.messageBody.setTextIsSelectable(true);
+			viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance());
 		} else {
 			viewHolder.messageBody.setText("");
 			viewHolder.messageBody.setTextIsSelectable(false);

src/main/java/eu/siacs/conversations/ui/widget/ClickableMovementMethod.java 🔗

@@ -0,0 +1,42 @@
+package eu.siacs.conversations.ui.widget;
+
+import android.text.Layout;
+import android.text.Spannable;
+import android.text.method.ArrowKeyMovementMethod;
+import android.text.style.ClickableSpan;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+public class ClickableMovementMethod extends ArrowKeyMovementMethod {
+
+	@Override
+	public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+		// Just copied from android.text.method.LinkMovementMethod
+		if (event.getAction() == MotionEvent.ACTION_UP) {
+			int x = (int) event.getX();
+			int y = (int) event.getY();
+			x -= widget.getTotalPaddingLeft();
+			y -= widget.getTotalPaddingTop();
+			x += widget.getScrollX();
+			y += widget.getScrollY();
+			Layout layout = widget.getLayout();
+			int line = layout.getLineForVertical(y);
+			int off = layout.getOffsetForHorizontal(line, x);
+			ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
+			if (link.length != 0) {
+				link[0].onClick(widget);
+				return true;
+			}
+		}
+		return super.onTouchEvent(widget, buffer, event);
+	}
+
+	public static ClickableMovementMethod getInstance() {
+		if (sInstance == null) {
+			sInstance = new ClickableMovementMethod();
+		}
+		return sInstance;
+	}
+
+	private static ClickableMovementMethod sInstance;
+}