proper swipe to reply handling
This commit is contained in:
parent
6c8d9c30ab
commit
abcdd96cc9
|
@ -1,95 +0,0 @@
|
|||
package com.cheogram.android;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.utils.Consumer;
|
||||
|
||||
// https://stackoverflow.com/a/41766670/8611
|
||||
/**
|
||||
* Created by hoshyar on 1/19/17.
|
||||
*/
|
||||
|
||||
public class SwipeDetector implements View.OnTouchListener {
|
||||
|
||||
protected Consumer<Action> cb;
|
||||
|
||||
private Set<Action> allowedActions;
|
||||
|
||||
private int touchSlop = -1;
|
||||
|
||||
public SwipeDetector(Consumer<Action> cb, Set<Action> allowedActions) {
|
||||
this.cb = cb;
|
||||
this.allowedActions = allowedActions;
|
||||
}
|
||||
|
||||
public static enum Action {
|
||||
LR, // Left to Right
|
||||
RL, // Right to Left
|
||||
None // when no action was detected
|
||||
}
|
||||
|
||||
private static final String logTag = "Swipe";
|
||||
private static final int MIN_DISTANCE = 100;
|
||||
private float downX, downY, upX, upY;
|
||||
private Action mSwipeDetected = Action.None;
|
||||
|
||||
public boolean swipeDetected() {
|
||||
return mSwipeDetected != Action.None;
|
||||
}
|
||||
|
||||
public Action getAction() {
|
||||
return mSwipeDetected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (touchSlop == -1) {
|
||||
touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
downX = event.getX();
|
||||
downY = event.getY();
|
||||
mSwipeDetected = Action.None;
|
||||
return false;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
upX = event.getX();
|
||||
upY = event.getY();
|
||||
|
||||
float deltaX = downX - upX;
|
||||
float deltaY = downY - upY;
|
||||
|
||||
if (
|
||||
(allowedActions.contains(Action.LR) && deltaX < -touchSlop ||
|
||||
allowedActions.contains(Action.RL) && deltaX > touchSlop) && Math.abs(deltaX) > Math.abs(deltaY)
|
||||
) {
|
||||
v.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
|
||||
if (Math.abs(deltaX) > dpToPx(MIN_DISTANCE)) {
|
||||
// left or right
|
||||
if (deltaX < 0 && allowedActions.contains(Action.LR)) {
|
||||
cb.accept(mSwipeDetected = Action.LR);
|
||||
return true;
|
||||
}
|
||||
if (deltaX > 0 && allowedActions.contains(Action.RL)) {
|
||||
cb.accept(mSwipeDetected = Action.RL);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int dpToPx(int dp) {
|
||||
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
|
||||
}
|
||||
}
|
62
src/main/java/eu/siacs/conversations/ui/DraggableListView.kt
Normal file
62
src/main/java/eu/siacs/conversations/ui/DraggableListView.kt
Normal file
|
@ -0,0 +1,62 @@
|
|||
package eu.siacs.conversations.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.widget.ListAdapter
|
||||
import android.widget.ListView
|
||||
import androidx.customview.widget.ViewDragHelper
|
||||
|
||||
class DraggableListView : ListView {
|
||||
constructor(context: Context?) : super(context)
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
)
|
||||
|
||||
private var dragHelper: ViewDragHelper? = null
|
||||
|
||||
override fun setAdapter(adapter: ListAdapter?) {
|
||||
super.setAdapter(adapter)
|
||||
val dragHelperCallback = if (adapter is DraggableAdapter) {
|
||||
adapter.getDragCallback()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
dragHelper = if (dragHelperCallback != null) {
|
||||
ViewDragHelper.create(this, dragHelperCallback)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (adapter is DraggableAdapter) {
|
||||
adapter.setViewDragHelper(dragHelper)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (dragHelper?.shouldInterceptTouchEvent(ev) == true) {
|
||||
return true
|
||||
}
|
||||
|
||||
return super.onInterceptTouchEvent(ev)
|
||||
}
|
||||
|
||||
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||
val res = dragHelper?.viewDragState != ViewDragHelper.STATE_DRAGGING && super.onTouchEvent(ev)
|
||||
return if (res) {
|
||||
true
|
||||
} else {
|
||||
dragHelper?.processTouchEvent(ev)
|
||||
dragHelper?.viewDragState == ViewDragHelper.STATE_DRAGGING
|
||||
}
|
||||
}
|
||||
|
||||
interface DraggableAdapter {
|
||||
fun getDragCallback(): ViewDragHelper.Callback?
|
||||
fun setViewDragHelper(helper: ViewDragHelper?)
|
||||
}
|
||||
}
|
23
src/main/java/eu/siacs/conversations/ui/LockedViewPager.kt
Normal file
23
src/main/java/eu/siacs/conversations/ui/LockedViewPager.kt
Normal file
|
@ -0,0 +1,23 @@
|
|||
package eu.siacs.conversations.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
|
||||
class LockedViewPager : ViewPager {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun canScrollHorizontally(direction: Int): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
|
@ -16,7 +15,6 @@ import android.text.SpannableString;
|
|||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextPaint;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.style.CharacterStyle;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
|
@ -25,7 +23,6 @@ import android.util.DisplayMetrics;
|
|||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
|
@ -41,17 +38,15 @@ import androidx.annotation.Nullable;
|
|||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.customview.widget.ViewDragHelper;
|
||||
|
||||
import com.cheogram.android.SwipeDetector;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import org.checkerframework.checker.units.qual.C;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -71,6 +66,7 @@ import eu.siacs.conversations.services.MessageArchiveService;
|
|||
import eu.siacs.conversations.services.NotificationService;
|
||||
import eu.siacs.conversations.ui.ConversationFragment;
|
||||
import eu.siacs.conversations.ui.ConversationsActivity;
|
||||
import eu.siacs.conversations.ui.DraggableListView;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import eu.siacs.conversations.ui.service.AudioPlayer;
|
||||
import eu.siacs.conversations.ui.text.DividerSpan;
|
||||
|
@ -92,7 +88,7 @@ import eu.siacs.conversations.xml.Element;
|
|||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||
|
||||
public class MessageAdapter extends ArrayAdapter<Message> {
|
||||
public class MessageAdapter extends ArrayAdapter<Message> implements DraggableListView.DraggableAdapter {
|
||||
|
||||
public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR";
|
||||
private static final int SENT = 0;
|
||||
|
@ -113,13 +109,67 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
private boolean mUseGreenBackground = false;
|
||||
private final boolean mForceNames;
|
||||
|
||||
private Set<SwipeDetector.Action> allowedSwipeActions;
|
||||
|
||||
@ColorInt
|
||||
private int primaryColor = -1;
|
||||
|
||||
private boolean allowRelativeTimestamps = true;
|
||||
|
||||
private ViewDragHelper dragHelper = null;
|
||||
private final ViewDragHelper.Callback dragCallback = new ViewDragHelper.Callback() {
|
||||
@Override
|
||||
public boolean tryCaptureView(@NonNull View child, int pointerId) {
|
||||
return child.getTag(R.id.TAG_DRAGGABLE) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
|
||||
if (dragHelper != null) {
|
||||
dragHelper.settleCapturedViewAt(0, releasedChild.getTop());
|
||||
ViewCompat.postOnAnimation(releasedChild, new SettleRunnable(releasedChild));
|
||||
ViewHolder viewHolder = (ViewHolder) releasedChild.getTag();
|
||||
|
||||
if (viewHolder != null && viewHolder.position >= 0 && viewHolder.position < getCount()) {
|
||||
Message m = getItem(viewHolder.position);
|
||||
if (messageBoxSwipedListener != null) {
|
||||
messageBoxSwipedListener.onMessageBoxSwiped(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onViewReleased(releasedChild, xvel, yvel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
|
||||
return Math.max(-child.getWidth()/4, Math.min(left, 0));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
|
||||
return child.getTop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewHorizontalDragRange(@NonNull View child) {
|
||||
return Math.max(Math.abs(child.getLeft()), 1);
|
||||
}
|
||||
|
||||
private class SettleRunnable implements Runnable {
|
||||
private View view;
|
||||
|
||||
public SettleRunnable(View view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (dragHelper != null && dragHelper.continueSettling(true)) {
|
||||
ViewCompat.postOnAnimation(view, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public MessageAdapter(final XmppActivity activity, final List<Message> messages, final boolean forceNames) {
|
||||
super(activity, 0, messages);
|
||||
this.audioPlayer = new AudioPlayer(this);
|
||||
|
@ -127,8 +177,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
metrics = getContext().getResources().getDisplayMetrics();
|
||||
updatePreferences();
|
||||
this.mForceNames = forceNames;
|
||||
allowedSwipeActions = new HashSet<>();
|
||||
allowedSwipeActions.add(SwipeDetector.Action.RL);
|
||||
|
||||
final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||
allowRelativeTimestamps = !p.getBoolean("always_full_timestamps", activity.getResources().getBoolean(R.bool.always_full_timestamps));
|
||||
|
@ -138,6 +186,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
this(activity, messages, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ViewDragHelper.Callback getDragCallback() {
|
||||
return dragCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setViewDragHelper(@Nullable ViewDragHelper helper) {
|
||||
this.dragHelper = helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return false;
|
||||
|
@ -761,10 +820,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
final Conversational conversation = message.getConversation();
|
||||
final Account account = conversation.getAccount();
|
||||
final int type = getItemViewType(position);
|
||||
|
||||
ViewHolder viewHolder;
|
||||
if (view == null) {
|
||||
viewHolder = new ViewHolder();
|
||||
|
||||
viewHolder.position = position;
|
||||
switch (type) {
|
||||
case DATE_SEPARATOR:
|
||||
view = activity.getLayoutInflater().inflate(R.layout.message_date_bubble, parent, false);
|
||||
|
@ -794,6 +854,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
viewHolder.time = view.findViewById(R.id.message_time);
|
||||
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
|
||||
viewHolder.audioPlayer = view.findViewById(R.id.audio_player);
|
||||
view.setTag(R.id.TAG_DRAGGABLE, true);
|
||||
break;
|
||||
case RECEIVED:
|
||||
view = activity.getLayoutInflater().inflate(R.layout.message_received, parent, false);
|
||||
|
@ -810,6 +871,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
|
||||
viewHolder.encryption = view.findViewById(R.id.message_encryption);
|
||||
viewHolder.audioPlayer = view.findViewById(R.id.audio_player);
|
||||
view.setTag(R.id.TAG_DRAGGABLE, true);
|
||||
break;
|
||||
case STATUS:
|
||||
view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
|
||||
|
@ -824,8 +886,14 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
view.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder) view.getTag();
|
||||
if (dragHelper.getCapturedView() == view) {
|
||||
dragHelper.abort();
|
||||
}
|
||||
|
||||
if (viewHolder == null) {
|
||||
return view;
|
||||
} else {
|
||||
viewHolder.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -924,19 +992,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
viewHolder.clicksInterceptor.setOnClickListener(messageItemClickListener);
|
||||
viewHolder.clicksInterceptor.setOnLongClickListener(messageItemLongClickListener);
|
||||
|
||||
|
||||
SwipeDetector swipeDetector = new SwipeDetector((action) -> {
|
||||
if (action == SwipeDetector.Action.RL && MessageAdapter.this.messageBoxSwipedListener != null) {
|
||||
MessageAdapter.this.messageBoxSwipedListener.onMessageBoxSwiped(message);
|
||||
}
|
||||
}, allowedSwipeActions);
|
||||
|
||||
viewHolder.root.setOnTouchListener(swipeDetector);
|
||||
viewHolder.message_box.setOnTouchListener(swipeDetector);
|
||||
viewHolder.messageBody.setOnTouchListener(swipeDetector);
|
||||
viewHolder.image.setOnTouchListener(swipeDetector);
|
||||
viewHolder.time.setOnTouchListener(swipeDetector);
|
||||
|
||||
viewHolder.contact_picture.setOnClickListener(v -> {
|
||||
if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
|
||||
MessageAdapter.this.mOnContactPictureClickedListener
|
||||
|
@ -1215,5 +1270,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
protected TextView encryption;
|
||||
|
||||
protected View clicksInterceptor;
|
||||
|
||||
int position;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
app:tabSelectedTextColor="@color/white"
|
||||
app:tabTextColor="@color/white70" />
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
<eu.siacs.conversations.ui.LockedViewPager
|
||||
android:id="@+id/conversation_view_pager"
|
||||
android:layout_below="@id/tab_layout"
|
||||
android:layout_width="fill_parent"
|
||||
|
@ -31,7 +31,7 @@
|
|||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
<ListView
|
||||
<eu.siacs.conversations.ui.DraggableListView
|
||||
android:id="@+id/messages_view"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -86,7 +86,9 @@
|
|||
<TextView
|
||||
android:id="@+id/context_preview_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="5"
|
||||
android:ellipsize="end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -269,7 +271,7 @@
|
|||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.viewpager.widget.ViewPager>
|
||||
</eu.siacs.conversations.ui.LockedViewPager>
|
||||
|
||||
</RelativeLayout>
|
||||
</layout>
|
|
@ -4,4 +4,5 @@
|
|||
<item type="id" name="TAG_FINGERPRINT"/>
|
||||
<item type="id" name="TAG_FINGERPRINT_STATUS"/>
|
||||
<item type="id" name="TAG_AUDIO_PLAYER_VIEW_HOLDER"/>
|
||||
<item type="id" name="TAG_DRAGGABLE"/>
|
||||
</resources>
|
Loading…
Reference in a new issue