diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/DeleteWhenCopiedSpan.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/DeleteWhenCopiedSpan.java new file mode 100644 index 00000000..02f40c2d --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/DeleteWhenCopiedSpan.java @@ -0,0 +1,8 @@ +package org.joinmastodon.android.ui.text; + +/** + * A span to mark character ranges that should be deleted when copied to the clipboard. + * Works with {@link org.joinmastodon.android.ui.views.LinkedTextView}. + */ +public class DeleteWhenCopiedSpan{ +} 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 ff8a47df..15f77d3a 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 @@ -111,7 +111,7 @@ public class HtmlParser{ if(node instanceof Element){ Element el=(Element)node; if("span".equals(el.nodeName()) && el.hasClass("ellipsis")){ - ssb.append('…'); + ssb.append("…", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); }else if("p".equals(el.nodeName())){ if(node.nextSibling()!=null) ssb.append("\n\n"); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/LinkedTextView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/LinkedTextView.java index 741ea84c..8e33e877 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/LinkedTextView.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/LinkedTextView.java @@ -1,38 +1,68 @@ package org.joinmastodon.android.ui.views; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.graphics.Canvas; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.util.AttributeSet; +import android.util.Log; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; import android.view.MotionEvent; import android.widget.TextView; import org.joinmastodon.android.ui.text.ClickableLinksDelegate; +import org.joinmastodon.android.ui.text.DeleteWhenCopiedSpan; -public class LinkedTextView extends TextView { +public class LinkedTextView extends TextView{ private ClickableLinksDelegate delegate=new ClickableLinksDelegate(this); private boolean needInvalidate; - - public LinkedTextView(Context context) { - super(context); - // TODO Auto-generated constructor stub + private ActionMode currentActionMode; + + public LinkedTextView(Context context){ + this(context, null); } - public LinkedTextView(Context context, AttributeSet attrs) { - super(context, attrs); - // TODO Auto-generated constructor stub + public LinkedTextView(Context context, AttributeSet attrs){ + this(context, attrs, 0); } - public LinkedTextView(Context context, AttributeSet attrs, int defStyle) { + public LinkedTextView(Context context, AttributeSet attrs, int defStyle){ super(context, attrs, defStyle); - // TODO Auto-generated constructor stub + setCustomSelectionActionModeCallback(new ActionMode.Callback(){ + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu){ + currentActionMode=mode; + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu){ + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item){ + onTextContextMenuItem(item.getItemId()); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode){ + currentActionMode=null; + } + }); } - + public boolean onTouchEvent(MotionEvent ev){ if(delegate.onTouch(ev)) return true; - return super.onTouchEvent(ev); + return super.onTouchEvent(ev); } - + public void onDraw(Canvas c){ super.onDraw(c); delegate.onDraw(c); @@ -47,4 +77,43 @@ public class LinkedTextView extends TextView { invalidate(); } + @Override + public boolean onTextContextMenuItem(int id){ + if(id==android.R.id.copy){ + final int selStart=getSelectionStart(); + final int selEnd=getSelectionEnd(); + int min=Math.max(0, Math.min(selStart, selEnd)); + int max=Math.max(0, Math.max(selStart, selEnd)); + final ClipData copyData=ClipData.newPlainText(null, deleteTextWithinDeleteSpans(getText().subSequence(min, max))); + ClipboardManager clipboard=getContext().getSystemService(ClipboardManager.class); + try { + clipboard.setPrimaryClip(copyData); + } catch (Throwable t) { + Log.w("LinkedTextView", t); + } + if(currentActionMode!=null){ + currentActionMode.finish(); + } + return true; + } + return super.onTextContextMenuItem(id); + } + + private CharSequence deleteTextWithinDeleteSpans(CharSequence text){ + if(text instanceof Spanned spanned){ + DeleteWhenCopiedSpan[] delSpans=spanned.getSpans(0, text.length(), DeleteWhenCopiedSpan.class); + if(delSpans.length>0){ + SpannableStringBuilder ssb=new SpannableStringBuilder(spanned); + for(DeleteWhenCopiedSpan span:delSpans){ + int start=ssb.getSpanStart(span); + int end=ssb.getSpanStart(span); + if(start==-1) + continue; + ssb.delete(start, end+1); + } + return ssb; + } + } + return text; + } }