com.appsimobile.appsii.SidebarHotspot.java Source code

Java tutorial

Introduction

Here is the source code for com.appsimobile.appsii.SidebarHotspot.java

Source

/*
 * Copyright 2015. Appsi Mobile
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.appsimobile.appsii;

import android.content.Context;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.support.v4.util.CircularArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;

import com.appsimobile.appsii.dagger.AppInjector;
import com.appsimobile.appsii.module.home.provider.HomeContract;
import com.appsimobile.util.ArrayUtils;

import java.lang.ref.WeakReference;

import javax.inject.Inject;

public class SidebarHotspot extends View {

    public static final int VIBRATE_DURATION = 20;

    static final int STATE_AWAITING_RELEASE = 3;

    private static final int STATE_WAITING = 0;

    private static final int STATE_GESTURE_IN_PROGRESS = 2;

    private static final int SIDEBAR_MINIMUM_MOVE = 0;

    final CircularArray<HotspotPageEntry> mHotspotPageEntries = new CircularArray<>(8);

    final Handler mHandler = new Handler();

    int mState = STATE_WAITING;

    boolean mSwipeInProgress;

    SidebarGestureCallback mCallback;

    boolean mIsDragOpening;

    float mStartX;

    float mStartY;

    float mRawStartY;

    SwipeListener mSwipeListener;

    ContentObserver mHotspotsPagesObserver;

    AsyncTask<Void, Void, CircularArray<HotspotPageEntry>> mLoadDataTask;
    /**
     * A shared preferences listener
     */
    SharedPreferencesListener mSharedPreferencesListener;
    @Inject
    Vibrator mVibrator;
    @Inject
    SharedPreferences mPreferences;
    private float mMinimumMove;
    private boolean mVibrate;
    private boolean mLeft;
    private int mTop;
    private int mLeftPos;
    private boolean mVisibleHotspots;
    /**
     * The hotspot-item this hotspot is bound to.
     * This contains the properties of the hotspot.
     */
    private HotspotItem mHotspotItem;
    /**
     * A velocity tracked given to the listeners so they can determine the speed if needed
     */
    private VelocityTracker mVelocityTracker;
    /**
     * The current background drawable
     */
    private Drawable mBackground;

    public SidebarHotspot(Context context) {
        super(context);
    }

    public SidebarHotspot(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SidebarHotspot(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public static Gesture detectAction(float deltaX, float deltaY, float minDistance) {
        Gesture swipeAction = null;
        if (deltaX >= minDistance) {
            swipeAction = Gesture.TO_CENTER;
        } else if (deltaY > minDistance) {
            swipeAction = Gesture.DOWN;
        } else if (deltaY < -minDistance) {
            swipeAction = Gesture.UP;
        }
        return swipeAction;

    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            mVelocityTracker = VelocityTracker.obtain();
            mVelocityTracker.addMovement(e);
            // remove the background to make sure it does not overlap
            // the sidebar

            mIsDragOpening = true;
            setBackgroundResource(0);

            float x = e.getX();
            float y = e.getY();

            if (mCallback != null) {
                mSwipeListener = mCallback.open(this, Gesture.TO_CENTER, (int) x, (int) y);
                mSwipeInProgress = mSwipeListener != null;
                mState = STATE_AWAITING_RELEASE;
                if (mVibrate) {
                    vibrate();
                }

                return true;
            }
            return false;
        }
        case MotionEvent.ACTION_MOVE:
            mVelocityTracker.addMovement(e);
            float x = e.getX();
            float y = e.getY();
            return detectSwipe(x, y, e);
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            cancelMotionHandling(e, false);
            return false;
        }

        return super.onTouchEvent(e);
    }

    private void vibrate() {
        mVibrator.vibrate(VIBRATE_DURATION);
    }

    private boolean detectSwipe(float x, float y, MotionEvent e) {
        if (mState == STATE_AWAITING_RELEASE && mSwipeInProgress) {
            if (mCallback != null) {
                mSwipeListener.setSwipeLocation(this, (int) e.getX(), (int) e.getY(), (int) e.getRawX(),
                        (int) e.getRawY());
            }
            return true;
        }
        if (mState != STATE_GESTURE_IN_PROGRESS)
            return false;
        float deltaX = Math.abs(x - mStartX);
        float deltaY = y - mStartY;
        Gesture action = detectAction(deltaX, deltaY, mMinimumMove);
        if (action != null) {
            mSwipeListener = mCallback.open(this, action, (int) x, (int) y);
            mSwipeInProgress = mSwipeListener != null;
            mState = STATE_AWAITING_RELEASE;
            return true;
        }
        return true;
    }

    private void cancelMotionHandling(MotionEvent e, boolean cancelled) {
        mState = STATE_WAITING;
        if (mSwipeListener != null) {
            if (e == null) {
                mSwipeListener.onSwipeEnd(this, 0, 0, cancelled, mVelocityTracker);
            } else {
                mSwipeListener.onSwipeEnd(this, (int) e.getRawX(), (int) e.getRawY(), cancelled, mVelocityTracker);
            }
        }
        if (mCallback != null) {
            mCallback.cancelVisualHints(this);
        }
        mSwipeInProgress = false;
        mSwipeListener = null;

        // restore the background
        setBackground(mBackground);
        mIsDragOpening = false;

        if (mCallback != null) {
            mCallback.removeIfNeeded(this);
        }
        invalidate();

        mVelocityTracker.addMovement(e);
        mVelocityTracker.recycle();
        mVelocityTracker = null;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mSwipeInProgress = false;

        if (mHotspotsPagesObserver == null) {
            mHotspotsPagesObserver = new ContentObserver(mHandler) {
                @Override
                public void onChange(boolean selfChange) {
                    onChange(selfChange, null);
                }

                @Override
                public void onChange(boolean selfChange, Uri uri) {
                    reloadHotspotData();
                }
            };
            getContext().getContentResolver().registerContentObserver(HomeContract.HotspotPages.CONTENT_URI, true,
                    mHotspotsPagesObserver);
        }

        setupBackground();
    }

    void reloadHotspotData() {

        Log.d("SidebarHotspot", "reloading hotspots");

        if (mHotspotItem == null)
            return;

        final long hotspotId = mHotspotItem.mId;

        final Context context = getContext();
        if (mLoadDataTask != null)
            mLoadDataTask.cancel(true);
        mLoadDataTask = new AsyncTask<Void, Void, CircularArray<HotspotPageEntry>>() {

            @Override
            protected CircularArray<HotspotPageEntry> doInBackground(Void... params) {

                Cursor c = context.getContentResolver().query(HotspotPagesQuery.createUri(hotspotId),
                        HotspotPagesQuery.PROJECTION, null, null, HomeContract.HotspotDetails.POSITION + " ASC");
                CircularArray<HotspotPageEntry> result = new CircularArray<>(c.getCount());
                while (c.moveToNext()) {
                    HotspotPageEntry entry = new HotspotPageEntry();
                    entry.mEnabled = c.getInt(HotspotPagesQuery.ENABLED) == 1;
                    entry.mPageId = c.getLong(HotspotPagesQuery.PAGE_ID);
                    entry.mHotspotId = c.getLong(HotspotPagesQuery.HOTSPOT_ID);
                    entry.mPageName = c.getString(HotspotPagesQuery.PAGE_NAME);
                    entry.mHotspotName = c.getString(HotspotPagesQuery.HOTSPOT_NAME);
                    entry.mPosition = c.getInt(HotspotPagesQuery.POSITION);
                    entry.mPageType = c.getInt(HotspotPagesQuery.PAGE_TYPE);
                    result.addLast(entry);
                }
                c.close();
                return result;
            }

            @Override
            protected void onPostExecute(CircularArray<HotspotPageEntry> hotspotPageEntries) {
                onHotspotEntriesLoaded(hotspotPageEntries);
            }
        };
        mLoadDataTask.execute();
    }

    private void setupBackground() {
        Drawable bg;
        if (mHotspotItem == null || !mVisibleHotspots) {
            mBackground = null;
            setBackground(null);
            return;
        }
        if (mHotspotItem.mLeft) {
            bg = getResources().getDrawable(R.drawable.floating_navigation_drawer_handle).mutate();
        } else {
            bg = getResources().getDrawable(R.drawable.floating_navigation_drawer_handle_right).mutate();
        }

        mBackground = bg;
        setBackground(bg);
    }

    void onHotspotEntriesLoaded(CircularArray<HotspotPageEntry> hotspotPageEntries) {
        mHotspotPageEntries.clear();
        ArrayUtils.addAll(mHotspotPageEntries, hotspotPageEntries);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mHotspotsPagesObserver != null) {
            getContext().getContentResolver().unregisterContentObserver(mHotspotsPagesObserver);
            mHotspotsPagesObserver = null;
        }
        if (mLoadDataTask != null) {
            mLoadDataTask.cancel(true);
        }

    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        init(getContext());
    }

    void init(Context context) {

        AppInjector.inject(this);

        float scale = AppsiApplication.getDensity(context);
        mMinimumMove = scale * SIDEBAR_MINIMUM_MOVE;

        SharedPreferences sharedPreferences = mPreferences;
        mVisibleHotspots = !sharedPreferences.getBoolean("pref_hide_hotspots", false);
        mSharedPreferencesListener = new SharedPreferencesListener(this);
        sharedPreferences.registerOnSharedPreferenceChangeListener(mSharedPreferencesListener);

        setClickable(true);

        setupBackground();
    }

    public CircularArray<HotspotPageEntry> getHotspotPageEntries() {
        return mHotspotPageEntries;
    }

    public void setVibrateOnTouch(boolean vibrate) {
        mVibrate = vibrate;
    }

    public void setCallback(SidebarGestureCallback callback) {
        mCallback = callback;
    }

    public void setPosition(boolean left, int x, int y) {
        mLeft = left;
        mTop = y;
        mLeftPos = x;
    }

    public boolean isLeft() {
        return mLeft;
    }

    public int getHPos() {
        return mLeftPos;
    }

    public int getVPos() {
        return mTop;
    }

    void setVisibleHotspotsEnabled(boolean visibleHotspotsEnabled) {
        mVisibleHotspots = visibleHotspotsEnabled;
        setupBackground();
        invalidate();
    }

    void bind(HotspotItem hotspotItem) {
        mHotspotItem = hotspotItem;
        setupBackground();
        reloadHotspotData();
    }

    long getHotspotId() {
        return mHotspotItem.mId;
    }

    public HotspotItem getConfiguration() {
        return mHotspotItem;
    }

    public enum Gesture {
        UP, DOWN, TO_CENTER, TAP, DOUBLE_TAP, LONG_PRESS,
    }

    public interface SidebarGestureCallback {

        /**
         * Show the swyper
         *
         * @return true if the swyper was added and the gesture must be tracked to it's end
         */
        SwipeListener open(SidebarHotspot hotspot, Gesture action, int x, int y);

        void cancelVisualHints(SidebarHotspot sidebarHotspot);

        SwipeListener longPressGesturePerformed(SidebarHotspot hotspot, int localX, int localY, int rawStartY);

        void removeIfNeeded(SidebarHotspot hotspot);
    }

    public interface SwipeListener {

        /**
         * Update the location of the swype with the raw x and y coordinates
         */
        void setSwipeLocation(SidebarHotspot hotspot, int localX, int localY, int screenX, int screenY);

        /**
         * @param hotspot
         * @param screenX
         * @param screenY
         */
        void onSwipeEnd(SidebarHotspot hotspot, int screenX, int screenY, boolean cancelled,
                VelocityTracker velocityTracker);
    }

    static class TapGestureDetector implements Handler.Callback {

        static final int mMinMovePx = 40;

        static final int WHAT_TAP_EVENT = 1;

        final int mMinMoveDip;

        final GestureDetector.OnDoubleTapListener mOnDoubleTapListener;

        final Handler mHandler;

        boolean mCouldBeTap;

        int mStartX;

        int mStartY;

        long mLastTapMillis = -1;

        int mLastTapX = -1;

        int mLastTapY = -1;

        TapGestureDetector(Context context, GestureDetector.OnDoubleTapListener doubleTapListener) {
            mMinMoveDip = (int) (mMinMovePx * context.getResources().getDisplayMetrics().density);
            mOnDoubleTapListener = doubleTapListener;
            mHandler = new Handler(this);
        }

        public boolean onTouchEvent(MotionEvent e) {
            switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mCouldBeTap = true;
                mStartX = (int) e.getX();
                mStartY = (int) e.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (!mCouldBeTap)
                    break;
                int x = (int) e.getX();
                int y = (int) e.getY();
                int deltaX = Math.abs(x - mStartX);
                int deltaY = Math.abs(y - mStartY);
                if (deltaX > mMinMoveDip || deltaY > mMinMoveDip) {
                    mCouldBeTap = false;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                mCouldBeTap = false;
                mLastTapMillis = -1;
                break;
            case MotionEvent.ACTION_UP:
                if (mCouldBeTap) {
                    int tapX = (int) e.getX();
                    int tapY = (int) e.getY();
                    return onTapEvent(tapX, tapY, e);
                }
                break;
            }
            return false;
        }

        private boolean onTapEvent(int x, int y, MotionEvent event) {
            cancelWatchForSigleTap();
            long time = System.currentTimeMillis();
            long tapDelta = Math.abs(mLastTapMillis - time);
            if (tapDelta > 50 && tapDelta < 300) {
                int deltaX = Math.abs(x - mLastTapX);
                int deltaY = Math.abs(y - mLastTapY);
                if (deltaX <= mMinMoveDip && deltaY <= mMinMoveDip) {
                    mOnDoubleTapListener.onDoubleTap(event);
                    mLastTapMillis = -1;
                    return true;
                }
            }
            startWatchForSingleTap(x, y, event);

            mLastTapMillis = time;
            mLastTapX = x;
            mLastTapY = y;
            return false;
        }

        private void cancelWatchForSigleTap() {
            mHandler.removeMessages(WHAT_TAP_EVENT);
        }

        private void startWatchForSingleTap(int x, int y, MotionEvent event) {
            Message message = mHandler.obtainMessage(WHAT_TAP_EVENT, x, y, event);
            mHandler.sendMessageDelayed(message, 100);
        }

        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
            case WHAT_TAP_EVENT:
                int x = msg.arg1;
                int y = msg.arg2;
                MotionEvent event = (MotionEvent) msg.obj;
                handleSigleTap(x, y, event);
                break;
            }
            return false;
        }

        private void handleSigleTap(int x, int y, MotionEvent event) {
            mOnDoubleTapListener.onSingleTapConfirmed(event);
        }

    }

    static class SharedPreferencesListener implements SharedPreferences.OnSharedPreferenceChangeListener {

        private final WeakReference<SidebarHotspot> mSidebarHotspot;

        SharedPreferencesListener(SidebarHotspot sidebarHotspot) {
            mSidebarHotspot = new WeakReference<>(sidebarHotspot);
        }

        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            SidebarHotspot hotspot = mSidebarHotspot.get();
            if (hotspot == null) {
                sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
                return;
            }

            if (key.equals("pref_hide_hotspots")) {
                boolean prefHideHotspots = sharedPreferences.getBoolean("pref_hide_hotspots", false);
                hotspot.setVisibleHotspotsEnabled(!prefHideHotspots);

            }
        }
    }

}