Merge branch 'dev_clickable_links_hold_to_copy'

This commit is contained in:
Grishka 2023-03-05 22:33:35 +03:00
commit a8ba50e762
1 changed files with 81 additions and 31 deletions

View File

@ -1,25 +1,37 @@
package org.joinmastodon.android.ui.text; package org.joinmastodon.android.ui.text;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.CornerPathEffect; import android.graphics.CornerPathEffect;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.os.Build;
import android.text.Layout; import android.text.Layout;
import android.text.Spanned; import android.text.Spanned;
import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.SoundEffectConstants; import android.view.SoundEffectConstants;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import org.joinmastodon.android.R;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ClickableLinksDelegate { public class ClickableLinksDelegate {
private Paint hlPaint; private final Paint hlPaint;
private Path hlPath; private Path hlPath;
private LinkSpan selectedSpan; private LinkSpan selectedSpan;
private TextView view; private final TextView view;
private final GestureDetector gestureDetector;
public ClickableLinksDelegate(TextView view) { public ClickableLinksDelegate(TextView view) {
this.view=view; this.view=view;
@ -27,10 +39,45 @@ public class ClickableLinksDelegate {
hlPaint.setAntiAlias(true); hlPaint.setAntiAlias(true);
hlPaint.setPathEffect(new CornerPathEffect(V.dp(3))); hlPaint.setPathEffect(new CornerPathEffect(V.dp(3)));
// view.setHighlightColor(view.getResources().getColor(android.R.color.holo_blue_light)); // view.setHighlightColor(view.getResources().getColor(android.R.color.holo_blue_light));
gestureDetector = new GestureDetector(view.getContext(), new LinkGestureListener(), view.getHandler());
} }
public boolean onTouch(MotionEvent event) { public boolean onTouch(MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN){ if(event.getAction()==MotionEvent.ACTION_CANCEL){
// the gestureDetector does not provide a callback for CANCEL, therefore:
// remove background color of view before passing event to gestureDetector
resetAndInvalidate();
}
return gestureDetector.onTouchEvent(event);
}
/**
* remove highlighting from span and let the system redraw the view
*/
private void resetAndInvalidate() {
hlPath=null;
selectedSpan=null;
view.invalidate();
}
public void onDraw(Canvas canvas){
if(hlPath!=null){
canvas.save();
canvas.translate(0, view.getPaddingTop());
canvas.drawPath(hlPath, hlPaint);
canvas.restore();
}
}
/**
* GestureListener for spans that represent URLs.
* onDown: on start of touch event, set highlighting
* onSingleTapUp: when there was a (short) tap, call onClick and reset highlighting
* onLongPress: copy URL to clipboard, let user know, reset highlighting
*/
private class LinkGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(@NonNull MotionEvent event) {
int line=-1; int line=-1;
Rect rect=new Rect(); Rect rect=new Rect();
Layout l=view.getLayout(); Layout l=view.getLayout();
@ -45,8 +92,7 @@ public class ClickableLinksDelegate {
return false; return false;
} }
CharSequence text=view.getText(); CharSequence text=view.getText();
if(text instanceof Spanned){ if(text instanceof Spanned s){
Spanned s=(Spanned)text;
LinkSpan[] spans=s.getSpans(0, s.length()-1, LinkSpan.class); LinkSpan[] spans=s.getSpans(0, s.length()-1, LinkSpan.class);
if(spans.length>0){ if(spans.length>0){
for(LinkSpan span:spans){ for(LinkSpan span:spans){
@ -88,31 +134,35 @@ public class ClickableLinksDelegate {
} }
} }
} }
return super.onDown(event);
} }
if(event.getAction()==MotionEvent.ACTION_UP && selectedSpan!=null){
view.playSoundEffect(SoundEffectConstants.CLICK); @Override
selectedSpan.onClick(view.getContext()); public boolean onSingleTapUp(@NonNull MotionEvent event) {
hlPath=null; if(selectedSpan!=null){
selectedSpan=null; view.playSoundEffect(SoundEffectConstants.CLICK);
view.invalidate(); selectedSpan.onClick(view.getContext());
resetAndInvalidate();
return true;
}
return false; return false;
} }
if(event.getAction()==MotionEvent.ACTION_CANCEL){
hlPath=null;
selectedSpan=null;
view.invalidate();
return false;
}
return false;
}
public void onDraw(Canvas canvas){ @Override
if(hlPath!=null){ public void onLongPress(@NonNull MotionEvent event) {
canvas.save(); //if target is not a link, don't copy
canvas.translate(0, view.getPaddingTop()); if (selectedSpan == null) return;
canvas.drawPath(hlPath, hlPaint); if (selectedSpan.getType() != LinkSpan.Type.URL) return;
canvas.restore(); //copy link text to clipboard
ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText("", selectedSpan.getLink()));
//show toast, android from S_V2 on has built-in popup, as documented in
//https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#duplicate-notifications
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
Toast.makeText(view.getContext(), R.string.text_copied, Toast.LENGTH_SHORT).show();
}
//reset view
resetAndInvalidate();
} }
} }
} }