gt: 		final int action = ev.getAction() & MotionEvent.ACTION_MASK;
context: /*******************************************************************************
   * This file is part of RedReader.
   *
   * RedReader is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * RedReader is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with RedReader.  If not, see <http://www.gnu.org/licenses/>.
   ******************************************************************************/
  
  package org.quantumbadger.redreader.views;
  
  import android.content.Context;
  import androidx.annotation.NonNull;
  import android.view.MotionEvent;
  import android.widget.FrameLayout;
  import org.quantumbadger.redreader.common.General;
  
  public abstract class SwipableItemView extends FrameLayout {
  
  	private MotionEvent mSwipeStart;
  	private int mSwipeStartPointerId = -1;
  
  	private boolean mSwipingEnabled = true;
  
  	private boolean mSwipeInProgress = false;
  
  	private float mCurrentSwipeDelta = 0;
  	private float mOverallSwipeDelta = 0;
  
  	private final SwipeHistory mSwipeHistory = new SwipeHistory(30);
  	private float mVelocity;
  
  	private SwipeAnimation mCurrentSwipeAnimation;
  
  	public SwipableItemView(@NonNull final Context context) {
  		super(context);
  	}
  
  	protected abstract void onSwipeFingerDown(
  			int x,
  			int y,
  			final float xOffsetPixels,
  			boolean wasOldSwipeInterrupted);
  
  	protected abstract void onSwipeDeltaChanged(float dx);
  
  	protected abstract boolean allowSwipingLeft();
  
  	protected abstract boolean allowSwipingRight();
  
  	public void setSwipingEnabled(final boolean swipingEnabled) {
  		mSwipingEnabled = swipingEnabled;
  	}
  
  	protected void resetSwipeState() {
  		mSwipeHistory.clear();
  		mSwipeStart = null;
  		mSwipeStartPointerId = -1;
  		mSwipeInProgress = false;
  		mCurrentSwipeDelta = 0;
  		mOverallSwipeDelta = 0;
  		cancelSwipeAnimation();
  
  		updateOffset();
  	}
  
  	private void updateOffset() {
  
  		final float overallPos = mOverallSwipeDelta + mCurrentSwipeDelta;
  
  		if((overallPos > 0 && !allowSwipingRight()) || (overallPos < 0
  				&& !allowSwipingLeft())) {
  			mOverallSwipeDelta = -mCurrentSwipeDelta;
  		}
  
  		onSwipeDeltaChanged(mOverallSwipeDelta + mCurrentSwipeDelta);
  	}
  
  	private void onFingerDown(final int x, final int y) {
  
  		final boolean wasOldSwipeInterrupted = (mCurrentSwipeAnimation != null) || (
  				mOverallSwipeDelta
  						!= 0);
  
  		cancelSwipeAnimation();
  		mSwipeHistory.clear();
  		mVelocity = 0;
  		mOverallSwipeDelta += mCurrentSwipeDelta;
  		mCurrentSwipeDelta = 0;
  		onSwipeFingerDown(x, y, mOverallSwipeDelta, wasOldSwipeInterrupted);
  	}
  
  	private void onFingerSwipeMove() {
  		mSwipeHistory.add(mCurrentSwipeDelta, System.currentTimeMillis());
  		updateOffset();
  	}
  
  	private void onSwipeEnd() {
  
  		if(mSwipeHistory.size() >= 2) {
  			mVelocity = (mSwipeHistory.getMostRecent()
  					- mSwipeHistory.getAtTimeAgoMs(100)) * 10;
  		} else {
  			mVelocity = 0;
  		}
  
  		mOverallSwipeDelta += mCurrentSwipeDelta;
  		mCurrentSwipeDelta = 0;
  
  		animateSwipeToRestPosition();
  	}
  
  	private void onSwipeCancelled() {
  
  		mVelocity = 0;
  
  		mOverallSwipeDelta += mCurrentSwipeDelta;
  		mCurrentSwipeDelta = 0;
  
  		animateSwipeToRestPosition();
  	}
  
  	private void animateSwipeToRestPosition() {
  		final LiveDHM.Params params
  				= new LiveDHM.Params(); // TODO account for screen dpi!
  		params.startPosition = mOverallSwipeDelta;
  		params.startVelocity = mVelocity;
  		startSwipeAnimation(new SwipeAnimation(params));
  	}
  
  	private void startSwipeAnimation(final SwipeAnimation animation) {
  
  		if(mCurrentSwipeAnimation != null) {
  			mCurrentSwipeAnimation.stop();
  		}
  
  		mCurrentSwipeAnimation = animation;
  		mCurrentSwipeAnimation.start();
  	}
  
  	private void cancelSwipeAnimation() {
  
  		if(mCurrentSwipeAnimation != null) {
  			mCurrentSwipeAnimation.stop();
  			mCurrentSwipeAnimation = null;
  		}
  	}
  
  	private class SwipeAnimation extends RRDHMAnimation {
  
  		private SwipeAnimation(final LiveDHM.Params params) {
  			super(params);
  		}
  
  		@Override
  		protected void onUpdatedPosition(final float position) {
  			mOverallSwipeDelta = position;
  			updateOffset();
  		}
  
  		@Override
  		protected void onEndPosition(final float endPosition) {
  			mOverallSwipeDelta = endPosition;
  			updateOffset();
  			mCurrentSwipeAnimation = null;
  		}
  
  	}
  
  	@Override
  	public boolean onInterceptTouchEvent(final MotionEvent ev) {
  
  		if(mSwipeInProgress) {
  			return true;
  		}
  
  		if(swipeStartLogic(ev)) {
  			return true;
  		}
  
  		return super.onInterceptTouchEvent(ev);
  	}
  
  	private boolean swipeStartLogic(final MotionEvent ev) {
  
  		if(mSwipeInProgress) {
  			throw new RuntimeException();
  		}
  
  		if(!mSwipingEnabled) {
  			return false;
  		}
  
  