diff --git a/clients/android/NewsBlur/res/values/strings.xml b/clients/android/NewsBlur/res/values/strings.xml index 2ee8acb2e..efd93ffd9 100644 --- a/clients/android/NewsBlur/res/values/strings.xml +++ b/clients/android/NewsBlur/res/values/strings.xml @@ -258,6 +258,8 @@ light + Gestures + Tidying up... Catching up %d reading actions... Catching up reading actions... @@ -300,4 +302,33 @@ NONE FOLDER_ONLY + + Rightward Swipe on Story Title + No Action + Mark Story Read + Mark Story Unread + + @string/gest_action_none + @string/gest_action_markread + @string/gest_action_markunread + + + GEST_ACTION_NONE + GEST_ACTION_MARKREAD + GEST_ACTION_MARKUNREAD + + GEST_ACTION_MARKREAD + + Leftward Swipe on Story Title + + @string/gest_action_none + @string/gest_action_markread + @string/gest_action_markunread + + + GEST_ACTION_NONE + GEST_ACTION_MARKREAD + GEST_ACTION_MARKUNREAD + + GEST_ACTION_MARKUNREAD diff --git a/clients/android/NewsBlur/res/xml/activity_settings.xml b/clients/android/NewsBlur/res/xml/activity_settings.xml index 0e758e967..830c8fb24 100644 --- a/clients/android/NewsBlur/res/xml/activity_settings.xml +++ b/clients/android/NewsBlur/res/xml/activity_settings.xml @@ -103,4 +103,22 @@ android:defaultValue="@string/default_theme_value" /> + + + + + diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ItemListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ItemListFragment.java index 813480575..3b8dc09c2 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ItemListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ItemListFragment.java @@ -37,6 +37,7 @@ import com.newsblur.service.NBSyncService; import com.newsblur.util.DefaultFeedView; import com.newsblur.util.FeedSet; import com.newsblur.util.FeedUtils; +import com.newsblur.util.GestureAction; import com.newsblur.util.PrefsUtils; import com.newsblur.util.ReadFilter; import com.newsblur.util.StoryOrder; @@ -64,6 +65,13 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis private View fleuronFooter; + // row index of the last story to get a LTR gesture or -1 if none + private int gestureLeftToRightFlag = -1; + // row index of the last story to get a RTL gesture or -1 if none + private int gestureRightToLeftFlag = -1; + // flag indicating a gesture just occurred so we can ignore spurious story taps right after + private boolean gestureDebounce = false; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -109,7 +117,7 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis itemList.addFooterView(fleuronFooter, null, false); itemList.setEmptyView(v.findViewById(R.id.empty_view)); - setupBezelSwipeDetector(itemList); + setupGestureDetector(itemList); itemList.setOnScrollListener(this); itemList.setOnItemClickListener(this); itemList.setOnCreateContextMenuListener(this); @@ -280,6 +288,10 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + // context menu like to get accidentally triggered by the ListView event handler right after + // we detect a gesure. if so, let the gesture happen rather than popping up the menu + if ((gestureLeftToRightFlag > -1) || (gestureRightToLeftFlag > -1)) return; + MenuInflater inflater = getActivity().getMenuInflater(); if (PrefsUtils.getStoryOrder(activity, getFeedSet()) == StoryOrder.NEWEST) { inflater.inflate(R.menu.context_story_newest, menu); @@ -353,6 +365,14 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis @Override public synchronized void onItemClick(AdapterView parent, View view, int position, long id) { + // clicks like to get accidentally triggered by the ListView event handler right after we detect + // a gesture. if so, let the gesture happen rather than popping up the menu + if (gestureDebounce){ + gestureDebounce = false; + return; + } + if ((gestureLeftToRightFlag > -1) || (gestureRightToLeftFlag > -1)) return; + int truePosition = position - 1; Story story = adapter.getStory(truePosition); if (getActivity().isFinishing()) return; @@ -364,36 +384,86 @@ public abstract class ItemListFragment extends NbFragment implements OnScrollLis adapter.setTextSize(size); adapter.notifyDataSetChanged(); } - } - protected void setupBezelSwipeDetector(View v) { - final GestureDetector gestureDetector = new GestureDetector(getActivity(), new BezelSwipeDetector()); + protected void setupGestureDetector(View v) { + final GestureDetector gestureDetector = new GestureDetector(getActivity(), new ItemListGestureDetector()); v.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { - return gestureDetector.onTouchEvent(event); + boolean result = gestureDetector.onTouchEvent(event); + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + ItemListFragment.this.flushGesture(); + } + return result; } }); } - /** - * A gesture detector that captures bezel swipes and finishes the activity, - * to simulate a 'back' gesture. - * - * NB: pretty much all Views still try to process on-tap events despite - * returning true, so be sure to check isFinishing() on all other - * tap handlers. - */ - class BezelSwipeDetector extends GestureDetector.SimpleOnGestureListener { + protected void gestureLeftToRight(float x, float y) { + int index = itemList.pointToPosition((int) x, (int) y); + gestureLeftToRightFlag = index; + } + + protected void gestureRightToLeft(float x, float y) { + int index = itemList.pointToPosition((int) x, (int) y); + gestureRightToLeftFlag = index; + } + + // the above gesture* methods will trigger more than once while being performed. it is not until + // the up-event that we look to see if any happened, and if so, take action and flush. + protected void flushGesture() { + int index = -1; + GestureAction action = GestureAction.GEST_ACTION_NONE; + if (gestureLeftToRightFlag > -1) { + index = gestureLeftToRightFlag; + action = PrefsUtils.getLeftToRightGestureAction(getActivity()); + gestureLeftToRightFlag = -1; + gestureDebounce = true; + } + if (gestureRightToLeftFlag > -1) { + index = gestureRightToLeftFlag; + action = PrefsUtils.getRightToLeftGestureAction(getActivity()); + gestureRightToLeftFlag = -1; + gestureDebounce = true; + } + if (index <= -1) return; + Story story = adapter.getStory(index-1); + switch (action) { + case GEST_ACTION_MARKREAD: + FeedUtils.markStoryAsRead(story, getActivity());; + break; + case GEST_ACTION_MARKUNREAD: + FeedUtils.markStoryUnread(story, getActivity());; + break; + case GEST_ACTION_NONE: + default: + } + } + + class ItemListGestureDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if((e1.getX() < 75f) && // the gesture should start from the left bezel and - ((e2.getX()-e1.getX()) > 90f) && // move horizontally to the right and - (Math.abs(e1.getY()-e2.getY()) < 40f) // have minimal vertical travel, so we don't capture scrolling gestures - ) { + if ((e1.getX() < 75f) && // the gesture should start from the left bezel and + ((e2.getX()-e1.getX()) > 90f) && // move horizontally to the right and + (Math.abs(e1.getY()-e2.getY()) < 40f) // have minimal vertical travel, so we don't capture scrolling gestures + ) { ItemListFragment.this.getActivity().finish(); return true; } + if ((e1.getX() > 75f) && // the gesture should not start from the left bezel and + ((e2.getX()-e1.getX()) > 120f) && // move horizontally to the right and + (Math.abs(e1.getY()-e2.getY()) < 40f) // have minimal vertical travel, so we don't capture scrolling gestures + ) { + ItemListFragment.this.gestureLeftToRight(e1.getX(), e1.getY()); + return true; + } + if ((e1.getX() > 75f) && // the gesture should not start from the left bezel and + ((e1.getX()-e2.getX()) > 120f) && // move horizontally to the left and + (Math.abs(e1.getY()-e2.getY()) < 40f) // have minimal vertical travel, so we don't capture scrolling gestures + ) { + ItemListFragment.this.gestureRightToLeft(e1.getX(), e1.getY()); + return true; + } return false; } } diff --git a/clients/android/NewsBlur/src/com/newsblur/util/GestureAction.java b/clients/android/NewsBlur/src/com/newsblur/util/GestureAction.java new file mode 100644 index 000000000..acb3b7927 --- /dev/null +++ b/clients/android/NewsBlur/src/com/newsblur/util/GestureAction.java @@ -0,0 +1,9 @@ +package com.newsblur.util; + +public enum GestureAction { + + GEST_ACTION_NONE, + GEST_ACTION_MARKREAD, + GEST_ACTION_MARKUNREAD; + +} diff --git a/clients/android/NewsBlur/src/com/newsblur/util/PrefConstants.java b/clients/android/NewsBlur/src/com/newsblur/util/PrefConstants.java index d66a0039e..fb8accc9a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/PrefConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/PrefConstants.java @@ -72,4 +72,7 @@ public class PrefConstants { public static final String VOLUME_KEY_NAVIGATION = "volume_key_navigation"; public static final String MARK_ALL_READ_CONFIRMATION = "pref_confirm_mark_all_read"; + + public static final String LTR_GESTURE_ACTION = "ltr_gesture_action"; + public static final String RTL_GESTURE_ACTION = "rtl_gesture_action"; } diff --git a/clients/android/NewsBlur/src/com/newsblur/util/PrefsUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/PrefsUtils.java index 4050790b7..f8a84a71d 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/PrefsUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/PrefsUtils.java @@ -599,4 +599,14 @@ public class PrefsUtils { SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0); return MarkAllReadConfirmation.valueOf(prefs.getString(PrefConstants.MARK_ALL_READ_CONFIRMATION, MarkAllReadConfirmation.FOLDER_ONLY.toString())); } + + public static GestureAction getLeftToRightGestureAction(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0); + return GestureAction.valueOf(prefs.getString(PrefConstants.LTR_GESTURE_ACTION, GestureAction.GEST_ACTION_MARKREAD.toString())); + } + + public static GestureAction getRightToLeftGestureAction(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0); + return GestureAction.valueOf(prefs.getString(PrefConstants.RTL_GESTURE_ACTION, GestureAction.GEST_ACTION_MARKUNREAD.toString())); + } }