Added drag&drop address bubbles

master
M66B 3 weeks ago
parent cec0f908f2
commit cb69b1d8fc

@ -29,6 +29,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@ -36,6 +37,7 @@ import android.net.Uri;
import android.os.Build;
import android.provider.ContactsContract;
import android.text.Editable;
import android.text.Layout;
import android.text.Spanned;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
@ -43,6 +45,8 @@ import android.text.TextWatcher;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
@ -71,6 +75,8 @@ public class EditTextMultiAutoComplete extends AppCompatMultiAutoCompleteTextVie
private boolean dark;
private int colorAccent;
private Tokenizer tokenizer;
private float dragDownX;
private float dragDownY;
private Map<String, Integer> encryption = new ConcurrentHashMap<>();
private static int[] icons = new int[]{
@ -100,6 +106,39 @@ public class EditTextMultiAutoComplete extends AppCompatMultiAutoCompleteTextVie
tokenizer = new CommaTokenizer();
setTokenizer(tokenizer);
setOnDragListener(new OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
try {
if (!(event.getLocalState() instanceof DragState))
return false;
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
return (event.getClipDescription() != null &&
event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN));
case DragEvent.ACTION_DRAG_ENTERED:
case DragEvent.ACTION_DRAG_LOCATION:
return true;
case DragEvent.ACTION_DROP:
return dropClip((DragState) event.getLocalState(), event.getX(), event.getY());
case DragEvent.ACTION_DRAG_EXITED:
case DragEvent.ACTION_DRAG_ENDED:
return true;
default:
return false;
}
} catch (Throwable ex) {
Log.e(ex);
return false;
}
}
});
prefs = PreferenceManager.getDefaultSharedPreferences(context);
dark = Helper.isDarkTheme(context);
colorAccent = Helper.resolveColor(context, androidx.appcompat.R.attr.colorAccent);
@ -195,6 +234,18 @@ public class EditTextMultiAutoComplete extends AppCompatMultiAutoCompleteTextVie
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
dragDownX = event.getX();
dragDownY = event.getY();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
dragDownX = 0;
dragDownY = 0;
break;
}
return super.onTouchEvent(event);
} catch (Throwable ex) {
Log.w(ex);
@ -202,6 +253,49 @@ public class EditTextMultiAutoComplete extends AppCompatMultiAutoCompleteTextVie
}
}
@Override
public boolean performLongClick() {
try {
Editable edit = getText();
Layout layout = getLayout();
if (edit == null || layout == null)
return super.performLongClick();
int line = layout.getLineForVertical((int) (dragDownY - getTotalPaddingTop() + getScrollY()));
float x = dragDownX - getTotalPaddingLeft() + getScrollX();
int offset = layout.getOffsetForHorizontal(line, x);
ClipImageSpan[] spans = edit.getSpans(offset, offset, ClipImageSpan.class);
if (spans.length != 1)
return super.performLongClick();
ClipImageSpan span = spans[0];
int start = edit.getSpanStart(span);
int end = edit.getSpanEnd(span);
if (start < 0 || end <= start)
return super.performLongClick();
Helper.performHapticFeedback(this, HapticFeedbackConstants.CONFIRM);
String text = edit.subSequence(start, end).toString();
DragState state = new DragState(this, span, start, end, text);
ClipData data = ClipData.newPlainText("FairEmail recipient", text);
View.DragShadowBuilder shadow = new ChipDragShadowBuilder(span);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
return startDragAndDrop(data, shadow, state, View.DRAG_FLAG_OPAQUE);
else {
startDrag(data, shadow, state, 0);
return true;
}
} catch (Throwable ex) {
Log.e(ex);
return false;
}
}
@Override
protected void replaceText(CharSequence text) {
HtmlHelper.clearComposingText(this);
@ -424,6 +518,82 @@ public class EditTextMultiAutoComplete extends AppCompatMultiAutoCompleteTextVie
return Math.max(start, selStart) <= Math.min(end, selEnd);
}
private boolean dropClip(DragState state, float x, float y) {
try {
Editable edit = getText();
Layout layout = getLayout();
if (edit == null || layout == null)
return false;
int target = getDropOffset(layout, x, y);
if (target < 0)
return false;
target = resolveDropBoundary(edit, target, state.span);
int sourceStart = state.start;
int sourceEnd = state.end;
if (this == state.source && target >= sourceStart && target <= sourceEnd)
return false;
String text = state.text;
int insertLength = text.length();
Editable source = state.source.getText();
source.removeSpan(state.span);
source.delete(sourceStart, sourceEnd);
state.source.invalidate();
state.source.post(state.source.update);
if (this == state.source && target > sourceEnd)
target -= sourceEnd - sourceStart;
target = Math.max(0, Math.min(target, edit.length()));
target = resolveDropBoundary(edit, target, null);
edit.insert(target, text);
edit.setSpan(state.span, target, target + insertLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setSelection(target + insertLength);
invalidate();
post(update);
return true;
} catch (Throwable ex) {
Log.e(ex);
return false;
}
}
private int getDropOffset(Layout layout, float x, float y) {
int line = layout.getLineForVertical((int) (y - getTotalPaddingTop() + getScrollY()));
line = Math.max(0, Math.min(line, layout.getLineCount() - 1));
float horizontal = x - getTotalPaddingLeft() + getScrollX();
horizontal = Math.max(layout.getLineLeft(line), Math.min(horizontal, layout.getLineRight(line)));
return layout.getOffsetForHorizontal(line, horizontal);
}
private int resolveDropBoundary(Editable edit, int target, @Nullable ClipImageSpan ignore) {
target = Math.max(0, Math.min(target, edit.length()));
ClipImageSpan[] spans = edit.getSpans(0, edit.length(), ClipImageSpan.class);
for (ClipImageSpan span : spans) {
if (span == ignore)
continue;
int start = edit.getSpanStart(span);
int end = edit.getSpanEnd(span);
if (start < 0 || end <= start)
continue;
if (target > start && target < end) {
int middle = start + (end - start) / 2;
return (target < middle ? start : end);
}
if (target == start || target == end)
return target;
}
return target;
}
private static class ClipImageSpan extends ImageSpan {
private boolean update;
@ -439,4 +609,45 @@ public class EditTextMultiAutoComplete extends AppCompatMultiAutoCompleteTextVie
return update;
}
}
private static class DragState {
final EditTextMultiAutoComplete source;
final ClipImageSpan span;
final int start;
final int end;
final String text;
DragState(EditTextMultiAutoComplete source, ClipImageSpan span, int start, int end, String text) {
this.source = source;
this.span = span;
this.start = start;
this.end = end;
this.text = text;
}
}
private static class ChipDragShadowBuilder extends View.DragShadowBuilder {
private final Drawable drawable;
ChipDragShadowBuilder(ClipImageSpan span) {
super();
drawable = span.getDrawable();
if (drawable != null)
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
}
@Override
public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
int width = Math.max(1, drawable.getBounds().width());
int height = Math.max(1, drawable.getBounds().height());
outShadowSize.set(width, height);
outShadowTouchPoint.set(width / 2, height / 2);
}
@Override
public void onDrawShadow(Canvas canvas) {
if (drawable != null)
drawable.draw(canvas);
}
}
}
Loading…
Cancel
Save