Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d5aef3db authored by Suphon Thanakornpakapong's avatar Suphon Thanakornpakapong
Browse files

Fix widget resize triggering while scrolling

parent 15dc8a71
Loading
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -209,6 +209,17 @@ public class Utilities {
        return defaultValue;
    }

    /**
     * Utility method to determine whether the given point, in local coordinates,
     * is inside the view, where the area of the view is expanded by the slop factor.
     * This method is called while processing touch-move events to determine if the event
     * is still within the view.
     */
    public static boolean pointInView(View v, float localX, float localY, float slop) {
        return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
                localY < (v.getHeight() + slop);
    }

    /**
     * Ensures that a value is within given bounds. Specifically:
     * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
+7 −31
Original line number Diff line number Diff line
@@ -86,40 +86,16 @@ public class RoundedWidgetView extends AppWidgetHostView {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "onInterceptTouchEvent() called with: ev = [" + ev.getAction() + "]");

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            mLongPressHelper.cancelLongPress();
        mLongPressHelper.onTouchEvent(ev);
        return mLongPressHelper.hasPerformedLongPress();
    }

        // Consume any touch events for ourselves after longpress is triggered
        if (mLongPressHelper.hasPerformedLongPress()) {
            mLongPressHelper.cancelLongPress();
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mLongPressHelper.onTouchEvent(event);
        return true;
    }

        // Watch for longpress events at this level to make sure
        // users can always pick up this widget
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                mLongPressHelper.postCheckForLongPress();
                break;
            }

            case MotionEvent.ACTION_UP:
                mLongPressHelper.cancelLongPress();
                break;

            case MotionEvent.ACTION_CANCEL:
                mLongPressHelper.cancelLongPress();
                break;
        }

        // Otherwise continue letting touch events fall through to children
        return false;

    }

    @Override
    public void cancelLongPress() {
        super.cancelLongPress();
+107 −21
Original line number Diff line number Diff line
@@ -16,54 +16,140 @@

package foundation.e.blisslauncher.features.widgets;

import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import foundation.e.blisslauncher.BlissLauncher;
import foundation.e.blisslauncher.core.Utilities;

/**
 * Utility class to handle tripper long press on a view with custom timeout and stylus event
 */
public class CheckLongPressHelper {
    private View mView;

    public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f;

    private final View mView;
    private final View.OnLongClickListener mListener;
    private final float mSlop;

    private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;

    private boolean mHasPerformedLongPress;
    private CheckForLongPress mPendingCheckForLongPress;

    private static final String TAG = "CheckLongPressHelper";
    private Runnable mPendingCheckForLongPress;

    class CheckForLongPress implements Runnable {
        public void run() {
            if ((mView.getParent() != null) && mView.hasWindowFocus()
                    && !mHasPerformedLongPress) {
                if (mView.performLongClick()) {
                    mView.setPressed(false);
                    mHasPerformedLongPress = true;
    public CheckLongPressHelper(View v) {
        this(v, null);
    }

    public CheckLongPressHelper(View v, View.OnLongClickListener listener) {
        mView = v;
        mListener = listener;
        mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
    }

    /**
     * Handles the touch event on a view
     *
     * @see View#onTouchEvent(MotionEvent)
     */
    public void onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                // Just in case the previous long press hasn't been cleared, we make sure to
                // start fresh on touch down.
                cancelLongPress();

                postCheckForLongPress();
                if (isStylusButtonPressed(ev)) {
                    triggerLongPress();
                }
                break;
            }
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                cancelLongPress();
                break;
            case MotionEvent.ACTION_MOVE:
                if (!Utilities.pointInView(mView, ev.getX(), ev.getY(), mSlop)) {
                    cancelLongPress();
                } else if (mPendingCheckForLongPress != null && isStylusButtonPressed(ev)) {
                    // Only trigger long press if it has not been cancelled before
                    triggerLongPress();
                }
                break;
        }
    }

    public CheckLongPressHelper(View v) {
        mView = v;
    /**
     * Overrides the default long press timeout.
     */
    public void setLongPressTimeoutFactor(float longPressTimeoutFactor) {
        mLongPressTimeoutFactor = longPressTimeoutFactor;
    }

    public void postCheckForLongPress() {
        Log.d(TAG, "postCheckForLongPress() called");
    private void postCheckForLongPress() {
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
            mPendingCheckForLongPress = this::triggerLongPress;
        }
        mView.postDelayed(mPendingCheckForLongPress, BlissLauncher.getLongPressTimeout());
        mView.postDelayed(mPendingCheckForLongPress,
                (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
    }

    /**
     * Cancels any pending long press
     */
    public void cancelLongPress() {
        Log.d(TAG, "cancelLongPress() called");
        mHasPerformedLongPress = false;
        clearCallbacks();
    }

    /**
     * Returns true if long press has been performed in the current touch gesture
     */
    public boolean hasPerformedLongPress() {
        return mHasPerformedLongPress;
    }

    private void triggerLongPress() {
        if ((mView.getParent() != null)
                && mView.hasWindowFocus()
                && (!mView.isPressed() || mListener != null)
                && !mHasPerformedLongPress) {
            boolean handled;
            if (mListener != null) {
                handled = mListener.onLongClick(mView);
            } else {
                handled = mView.performLongClick();
            }
            if (handled) {
                mView.setPressed(false);
                mHasPerformedLongPress = true;
            }
            clearCallbacks();
        }
    }

    private void clearCallbacks() {
        if (mPendingCheckForLongPress != null) {
            mView.removeCallbacks(mPendingCheckForLongPress);
            mPendingCheckForLongPress = null;
        }
    }

    public boolean hasPerformedLongPress() {
        return mHasPerformedLongPress;

    /**
     * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
     * pressed.
     *
     * @param event The event to check.
     * @return Whether a stylus button press occurred.
     */
    private static boolean isStylusButtonPressed(MotionEvent event) {
        return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
                && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
    }
}