diff --git a/opentasks/src/main/java/org/dmfs/tasks/linkify/ActionModeLinkify.java b/opentasks/src/main/java/org/dmfs/tasks/linkify/ActionModeLinkify.java index 62a881c6c..d11fc54fc 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/linkify/ActionModeLinkify.java +++ b/opentasks/src/main/java/org/dmfs/tasks/linkify/ActionModeLinkify.java @@ -70,16 +70,18 @@ public static void linkify(TextView textView, ActionModeListener listener) Matcher m = LINK_PATTERN.matcher(text); SpannableString s = new SpannableString(text); new ForEach<>(new Seq<>(s.getSpans(0, s.length(), ClickableSpan.class))).process(s::removeSpan); - while (m.find()) + int pos = 0; + while (m.find(pos)) { int start = m.start(1); int end = m.end(1); + pos = end; Uri uri = null; if (m.group(2) != null) { uri = Uri.parse("tel:" + PhoneNumberUtils.normalizeNumber(m.group(2))); } - if (m.group(3) != null) + else if (m.group(3) != null) { uri = Uri.parse(m.group(3)); if (uri.getScheme() == null) @@ -87,7 +89,7 @@ public static void linkify(TextView textView, ActionModeListener listener) uri = uri.buildUpon().scheme("https").build(); } } - if (m.group(4) != null) + else if (m.group(4) != null) { uri = Uri.parse("mailto:" + m.group(4)); } @@ -158,7 +160,10 @@ public void onGetContentRect(ActionMode mode, View view, Rect outRect) }; ActionMode am = textView.startActionMode(actionMode, android.view.ActionMode.TYPE_FLOATING); - closeActionTrigger.subscribe(am::finish); + if (am != null) + { + closeActionTrigger.subscribe(am::finish); + } } } }, diff --git a/opentasks/src/main/java/org/dmfs/tasks/utils/DescriptionMovementMethod.java b/opentasks/src/main/java/org/dmfs/tasks/utils/DescriptionMovementMethod.java new file mode 100644 index 000000000..26684a22c --- /dev/null +++ b/opentasks/src/main/java/org/dmfs/tasks/utils/DescriptionMovementMethod.java @@ -0,0 +1,87 @@ +package org.dmfs.tasks.utils; + +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.method.ArrowKeyMovementMethod; +import android.text.method.MovementMethod; +import android.text.style.ClickableSpan; +import android.view.MotionEvent; +import android.widget.TextView; + + +/** + * A movement method which allows moving the cursor with the arrow keys while still providing clickable links. + *

+ * TODO: provide a way to act on entering or leaving a Clickable span with the cursor. + */ +public class DescriptionMovementMethod extends ArrowKeyMovementMethod +{ + @Override + public boolean canSelectArbitrarily() + { + return true; + } + + + @Override + public boolean onTouchEvent(TextView widget, Spannable spannable, MotionEvent event) + { + int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) + { + 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[] links = spannable.getSpans(off, off, ClickableSpan.class); + + if (links.length != 0) + { + ClickableSpan link = links[0]; + if (action == MotionEvent.ACTION_UP) + { + if (link instanceof ClickableSpan) + { + link.onClick(widget); + } + } + else if (action == MotionEvent.ACTION_DOWN) + { + Selection.setSelection(spannable, spannable.getSpanStart(link), spannable.getSpanEnd(link)); + } + return true; + } + else + { + Selection.removeSelection(spannable); + } + } + + return super.onTouchEvent(widget, spannable, event); + } + + + public static MovementMethod getInstance() + { + if (sInstance == null) + { + sInstance = new DescriptionMovementMethod(); + } + + return sInstance; + } + + + private static DescriptionMovementMethod sInstance; +} diff --git a/opentasks/src/main/java/org/dmfs/tasks/widget/DescriptionFieldView.java b/opentasks/src/main/java/org/dmfs/tasks/widget/DescriptionFieldView.java index a1eea171f..eca15c9d5 100644 --- a/opentasks/src/main/java/org/dmfs/tasks/widget/DescriptionFieldView.java +++ b/opentasks/src/main/java/org/dmfs/tasks/widget/DescriptionFieldView.java @@ -25,7 +25,6 @@ import android.net.Uri; import android.text.Editable; import android.text.TextWatcher; -import android.text.method.LinkMovementMethod; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -59,6 +58,7 @@ import org.dmfs.tasks.model.FieldDescriptor; import org.dmfs.tasks.model.adapters.DescriptionFieldAdapter; import org.dmfs.tasks.model.layout.LayoutOptions; +import org.dmfs.tasks.utils.DescriptionMovementMethod; import java.util.List; @@ -242,7 +242,7 @@ private View createItemView() transition.disableTransitionType(LayoutTransition.CHANGING); transition.disableTransitionType(LayoutTransition.APPEARING); transition.disableTransitionType(LayoutTransition.DISAPPEARING); - ((TextView) item.findViewById(android.R.id.title)).setMovementMethod(LinkMovementMethod.getInstance()); + ((TextView) item.findViewById(android.R.id.title)).setMovementMethod(DescriptionMovementMethod.getInstance()); return item; }