package ml.docilealligator.infinityforreddit.customviews; import android.content.Context; import android.content.SharedPreferences; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import androidx.annotation.Nullable; import com.google.android.material.floatingactionbutton.FloatingActionButton; import ml.docilealligator.infinityforreddit.customviews.slidr.widget.SliderPanel; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; public class MovableFloatingActionButton extends FloatingActionButton implements View.OnTouchListener { private final static float CLICK_DRAG_TOLERANCE = 50; private long downTime = 0; private boolean moved = false; private boolean longClicked = false; private float downRawX, downRawY; private float dX, dY; @Nullable private Display display; @Nullable private SharedPreferences postDetailsSharedPreferences; private boolean portrait; @Nullable private SliderPanel sliderPanel; public MovableFloatingActionButton(Context context) { super(context); init(); } public MovableFloatingActionButton(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MovableFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { setOnTouchListener(this); new Handler(Looper.getMainLooper()).post(() -> { ViewParent parent = getParent(); while (parent != null) { if (parent instanceof SliderPanel) { sliderPanel = (SliderPanel) parent; break; } parent = parent.getParent(); } }); } @Override public boolean onTouch(View view, MotionEvent motionEvent) { ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); int action = motionEvent.getAction(); if (action == MotionEvent.ACTION_DOWN) { downTime = System.currentTimeMillis(); moved = false; downRawX = motionEvent.getRawX(); downRawY = motionEvent.getRawY(); dX = view.getX() - downRawX; dY = view.getY() - downRawY; if (sliderPanel != null) { sliderPanel.lock(); } return true; } else if (action == MotionEvent.ACTION_MOVE) { if (!moved) { if (System.currentTimeMillis() - downTime >= 300) { if (!longClicked) { longClicked = true; return performLongClick(); } else { moved = true; } } float upRawX = motionEvent.getRawX(); float upRawY = motionEvent.getRawY(); float upDX = upRawX - downRawX; float upDY = upRawY - downRawY; if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { return true; } else { moved = true; } } int viewWidth = view.getWidth(); int viewHeight = view.getHeight(); View viewParent = (View) view.getParent(); int parentWidth = viewParent.getWidth(); int parentHeight = viewParent.getHeight(); float newX = motionEvent.getRawX() + dX; newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent float newY = motionEvent.getRawY() + dY; newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent saveCoordinates(newX, newY); view.animate() .x(newX) .y(newY) .setDuration(0) .start(); return true; } else if (action == MotionEvent.ACTION_UP) { if (longClicked) { longClicked = false; return true; } float upRawX = motionEvent.getRawX(); float upRawY = motionEvent.getRawY(); float upDX = upRawX - downRawX; float upDY = upRawY - downRawY; if (sliderPanel != null) { sliderPanel.unlock(); } if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { return System.currentTimeMillis() - downTime >= 300 ? performLongClick() : performClick(); } else { return true; } } else { if (sliderPanel != null) { sliderPanel.unlock(); } return super.onTouchEvent(motionEvent); } } private void setPositionEnsureVisibility(float newX, float newY) { ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams(); View viewParent = (View) getParent(); int parentWidth = viewParent.getWidth(); int parentHeight = viewParent.getHeight(); int viewWidth = getWidth(); int viewHeight = getHeight(); newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent setX(newX); setY(newY); } public void bindRequiredData(@Nullable Display display, SharedPreferences postDetailsSharedPreferences, boolean portrait) { this.display = display; this.postDetailsSharedPreferences = postDetailsSharedPreferences; this.portrait = portrait; } public void setCoordinates() { if (postDetailsSharedPreferences == null) { return; } if (portrait) { if (postDetailsSharedPreferences.contains(SharedPreferencesUtils.getPostDetailFabPortraitX(display)) && postDetailsSharedPreferences.contains(SharedPreferencesUtils.getPostDetailFabPortraitY(display))) { setPositionEnsureVisibility(postDetailsSharedPreferences.getFloat(SharedPreferencesUtils.getPostDetailFabPortraitX(display), 0), postDetailsSharedPreferences.getFloat(SharedPreferencesUtils.getPostDetailFabPortraitY(display), 0)); } } else { if (postDetailsSharedPreferences.contains(SharedPreferencesUtils.getPostDetailFabLandscapeX(display)) && postDetailsSharedPreferences.contains(SharedPreferencesUtils.getPostDetailFabLandscapeY(display))) { setPositionEnsureVisibility(postDetailsSharedPreferences.getFloat(SharedPreferencesUtils.getPostDetailFabLandscapeX(display), 0), postDetailsSharedPreferences.getFloat(SharedPreferencesUtils.getPostDetailFabLandscapeY(display), 0)); } } } public void resetCoordinates() { if (portrait) { if (postDetailsSharedPreferences != null) { postDetailsSharedPreferences .edit() .remove(SharedPreferencesUtils.getPostDetailFabPortraitX(display)) .remove(SharedPreferencesUtils.getPostDetailFabPortraitY(display)) .apply(); } } else { if (postDetailsSharedPreferences != null) { postDetailsSharedPreferences .edit() .remove(SharedPreferencesUtils.getPostDetailFabLandscapeX(display)) .remove(SharedPreferencesUtils.getPostDetailFabLandscapeY(display)) .apply(); } } setTranslationX(0); setTranslationY(0); } private void saveCoordinates(float x, float y) { if (postDetailsSharedPreferences == null) { return; } if (portrait) { postDetailsSharedPreferences.edit().putFloat(SharedPreferencesUtils.getPostDetailFabPortraitX(display), x) .putFloat(SharedPreferencesUtils.getPostDetailFabPortraitY(display), y) .apply(); } else { postDetailsSharedPreferences.edit().putFloat(SharedPreferencesUtils.getPostDetailFabLandscapeX(display), x) .putFloat(SharedPreferencesUtils.getPostDetailFabLandscapeY(display), y) .apply(); } } }