diff --git a/src/com/fsck/k9/activity/K9ListActivity.java b/src/com/fsck/k9/activity/K9ListActivity.java index 4c01d49df..91427e505 100644 --- a/src/com/fsck/k9/activity/K9ListActivity.java +++ b/src/com/fsck/k9/activity/K9ListActivity.java @@ -1,7 +1,9 @@ package com.fsck.k9.activity; import android.util.Log; +import android.view.GestureDetector; import android.view.KeyEvent; +import android.view.MotionEvent; import android.widget.AdapterView; import android.widget.ListView; import android.os.Bundle; @@ -11,6 +13,8 @@ import com.fsck.k9.K9; import com.fsck.k9.helper.DateFormatter; public class K9ListActivity extends SherlockListActivity { + protected GestureDetector mGestureDetector; + @Override public void onCreate(Bundle icicle) { K9Activity.setLanguage(this, K9.getK9Language()); @@ -88,4 +92,12 @@ public class K9ListActivity extends SherlockListActivity { } return super.onKeyUp(keyCode, event); } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (mGestureDetector != null) { + mGestureDetector.onTouchEvent(ev); + } + return super.dispatchTouchEvent(ev); + } } diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java index 87c2bcc88..27f2baa0a 100644 --- a/src/com/fsck/k9/activity/MessageList.java +++ b/src/com/fsck/k9/activity/MessageList.java @@ -62,6 +62,8 @@ import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.R; import com.fsck.k9.SearchSpecification; +import com.fsck.k9.activity.misc.SwipeGestureDetector; +import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener; import com.fsck.k9.activity.setup.AccountSettings; import com.fsck.k9.activity.setup.FolderSettings; import com.fsck.k9.activity.setup.Prefs; @@ -84,9 +86,9 @@ import com.fsck.k9.mail.store.StorageManager; * shows a list of messages. * From this Activity the user can perform all standard message operations. */ -public class MessageList - extends K9Activity - implements OnClickListener, AdapterView.OnItemClickListener, AnimationListener, OnNavigationListener { +public class MessageList extends K9ListActivity implements OnClickListener, + AdapterView.OnItemClickListener, AnimationListener, OnNavigationListener, + OnSwipeGestureListener { /** * Reverses the result of a {@link Comparator}. @@ -301,13 +303,13 @@ public class MessageList private boolean mCheckboxes = true; private int mSelectedCount = 0; - private View mBatchButtonArea; - private ImageButton mBatchReadButton; - private ImageButton mBatchDeleteButton; - private ImageButton mBatchFlagButton; - private ImageButton mBatchArchiveButton; - private ImageButton mBatchMoveButton; - private ImageButton mBatchDoneButton; +// private View mBatchButtonArea; +// private ImageButton mBatchReadButton; +// private ImageButton mBatchDeleteButton; +// private ImageButton mBatchFlagButton; +// private ImageButton mBatchArchiveButton; +// private ImageButton mBatchMoveButton; +// private ImageButton mBatchDoneButton; private FontSizes mFontSizes = K9.getFontSizes(); @@ -691,7 +693,7 @@ public class MessageList initializeMessageList(getIntent(), true); // Enable gesture detection for MessageLists - mGestureDetector = new GestureDetector(new MyGestureDetector(true)); + mGestureDetector = new GestureDetector(new SwipeGestureDetector(this, this)); } @Override @@ -850,12 +852,12 @@ public class MessageList mController.listLocalMessages(mAccount, mFolderName, mAdapter.mListener); // Hide the archive button if we don't have an archive folder. if (!mAccount.hasArchiveFolder()) { - mBatchArchiveButton.setVisibility(View.GONE); +// mBatchArchiveButton.setVisibility(View.GONE); } } else if (mQueryString != null) { mController.searchLocalMessages(mAccountUuids, mFolderNames, null, mQueryString, mIntegrate, mQueryFlags, mForbiddenFlags, mAdapter.mListener); // Don't show the archive button if this is a search. - mBatchArchiveButton.setVisibility(View.GONE); +// mBatchArchiveButton.setVisibility(View.GONE); } } else { @@ -920,9 +922,8 @@ public class MessageList } private void initializeLayout() { - setContentView(R.layout.message_list); - - mListView = (ListView) findViewById(R.id.message_list); + mListView = getListView(); + mListView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET); mListView.setLongClickable(true); mListView.setFastScrollEnabled(true); mListView.setScrollingCacheEnabled(false); @@ -931,26 +932,26 @@ public class MessageList registerForContextMenu(mListView); - mBatchButtonArea = findViewById(R.id.batch_button_area); - mBatchReadButton = (ImageButton) findViewById(R.id.batch_read_button); - mBatchReadButton.setOnClickListener(this); - mBatchDeleteButton = (ImageButton) findViewById(R.id.batch_delete_button); - mBatchDeleteButton.setOnClickListener(this); - mBatchFlagButton = (ImageButton) findViewById(R.id.batch_flag_button); - mBatchFlagButton.setOnClickListener(this); - mBatchArchiveButton = (ImageButton) findViewById(R.id.batch_archive_button); - mBatchArchiveButton.setOnClickListener(this); - mBatchMoveButton = (ImageButton) findViewById(R.id.batch_move_button); - mBatchMoveButton.setOnClickListener(this); - mBatchDoneButton = (ImageButton) findViewById(R.id.batch_done_button); - mBatchDoneButton.setOnClickListener(this); - - mBatchReadButton.setVisibility(K9.batchButtonsMarkRead() ? View.VISIBLE : View.GONE); - mBatchDeleteButton.setVisibility(K9.batchButtonsDelete() ? View.VISIBLE : View.GONE); - mBatchArchiveButton.setVisibility(K9.batchButtonsArchive() ? View.VISIBLE : View.GONE); - mBatchMoveButton.setVisibility(K9.batchButtonsMove() ? View.VISIBLE : View.GONE); - mBatchFlagButton.setVisibility(K9.batchButtonsFlag() ? View.VISIBLE : View.GONE); - mBatchDoneButton.setVisibility(K9.batchButtonsUnselect() ? View.VISIBLE : View.GONE); +// mBatchButtonArea = findViewById(R.id.batch_button_area); +// mBatchReadButton = (ImageButton) findViewById(R.id.batch_read_button); +// mBatchReadButton.setOnClickListener(this); +// mBatchDeleteButton = (ImageButton) findViewById(R.id.batch_delete_button); +// mBatchDeleteButton.setOnClickListener(this); +// mBatchFlagButton = (ImageButton) findViewById(R.id.batch_flag_button); +// mBatchFlagButton.setOnClickListener(this); +// mBatchArchiveButton = (ImageButton) findViewById(R.id.batch_archive_button); +// mBatchArchiveButton.setOnClickListener(this); +// mBatchMoveButton = (ImageButton) findViewById(R.id.batch_move_button); +// mBatchMoveButton.setOnClickListener(this); +// mBatchDoneButton = (ImageButton) findViewById(R.id.batch_done_button); +// mBatchDoneButton.setOnClickListener(this); +// +// mBatchReadButton.setVisibility(K9.batchButtonsMarkRead() ? View.VISIBLE : View.GONE); +// mBatchDeleteButton.setVisibility(K9.batchButtonsDelete() ? View.VISIBLE : View.GONE); +// mBatchArchiveButton.setVisibility(K9.batchButtonsArchive() ? View.VISIBLE : View.GONE); +// mBatchMoveButton.setVisibility(K9.batchButtonsMove() ? View.VISIBLE : View.GONE); +// mBatchFlagButton.setVisibility(K9.batchButtonsFlag() ? View.VISIBLE : View.GONE); +// mBatchDoneButton.setVisibility(K9.batchButtonsUnselect() ? View.VISIBLE : View.GONE); mActionBarProgressView = mInflater.inflate(R.layout.actionbar_indeterminate_progress, null); } @@ -1046,14 +1047,14 @@ public class MessageList return false; } case KeyEvent.KEYCODE_DPAD_LEFT: { - if (mBatchButtonArea.hasFocus()) { + if (false /*mBatchButtonArea.hasFocus()*/) { return false; } else { return true; } } case KeyEvent.KEYCODE_DPAD_RIGHT: { - if (mBatchButtonArea.hasFocus()) { + if (false /*mBatchButtonArea.hasFocus()*/) { return false; } else { return true; @@ -1799,13 +1800,13 @@ public class MessageList } @Override - protected void onSwipeRightToLeft(final MotionEvent e1, final MotionEvent e2) { + public void onSwipeRightToLeft(final MotionEvent e1, final MotionEvent e2) { // Handle right-to-left as an un-select handleSwipe(e1, false); } @Override - protected void onSwipeLeftToRight(final MotionEvent e1, final MotionEvent e2) { + public void onSwipeLeftToRight(final MotionEvent e1, final MotionEvent e2) { // Handle left-to-right as a select. handleSwipe(e1, true); } @@ -2623,20 +2624,20 @@ public class MessageList } private void hideBatchButtons() { - if (mBatchButtonArea.getVisibility() != View.GONE) { - mBatchButtonArea.setVisibility(View.GONE); - mBatchButtonArea.startAnimation( - AnimationUtils.loadAnimation(this, R.anim.footer_disappear)); - } +// if (mBatchButtonArea.getVisibility() != View.GONE) { +// mBatchButtonArea.setVisibility(View.GONE); +// mBatchButtonArea.startAnimation( +// AnimationUtils.loadAnimation(this, R.anim.footer_disappear)); +// } } private void showBatchButtons() { - if (mBatchButtonArea.getVisibility() != View.VISIBLE) { - mBatchButtonArea.setVisibility(View.VISIBLE); - Animation animation = AnimationUtils.loadAnimation(this, R.anim.footer_appear); - animation.setAnimationListener(this); - mBatchButtonArea.startAnimation(animation); - } +// if (mBatchButtonArea.getVisibility() != View.VISIBLE) { +// mBatchButtonArea.setVisibility(View.VISIBLE); +// Animation animation = AnimationUtils.loadAnimation(this, R.anim.footer_appear); +// animation.setAnimationListener(this); +// mBatchButtonArea.startAnimation(animation); +// } } private void toggleBatchButtons() { @@ -2672,8 +2673,8 @@ public class MessageList showBatchButtons(); } - mBatchReadButton.setImageResource(readButtonIconId); - mBatchFlagButton.setImageResource(flagButtonIconId); +// mBatchReadButton.setImageResource(readButtonIconId); +// mBatchFlagButton.setImageResource(flagButtonIconId); } @@ -2720,62 +2721,62 @@ public class MessageList @Override public void onClick(View v) { - boolean newState = false; - List messageList = new ArrayList(); - List removeHolderList = new ArrayList(); - - if (v == mBatchDoneButton) { - setAllSelected(false); - return; - } - - if (v == mBatchFlagButton) { - newState = computeBatchDirection(true); - } else { - newState = computeBatchDirection(false); - } - - if (v == mBatchArchiveButton) { - final List selection = getSelectionFromCheckboxes(); - onArchive(selection); - return; - } - - if (v == mBatchMoveButton) { - final List selection = getSelectionFromCheckboxes(); - onMove(selection); - return; - } - - for (MessageInfoHolder holder : mAdapter.getMessages()) { - if (holder.selected) { - if (v == mBatchDeleteButton) { - removeHolderList.add(holder); - } else if (v == mBatchFlagButton) { - holder.flagged = newState; - } else if (v == mBatchReadButton) { - holder.read = newState; - } - messageList.add(holder.message); - } - } - - mAdapter.removeMessages(removeHolderList); - - if (!messageList.isEmpty()) { - if (v == mBatchDeleteButton) { - mController.deleteMessages(messageList.toArray(EMPTY_MESSAGE_ARRAY), null); - mSelectedCount = 0; - toggleBatchButtons(); - } else { - mController.setFlag(messageList.toArray(EMPTY_MESSAGE_ARRAY), (v == mBatchReadButton ? Flag.SEEN : Flag.FLAGGED), newState); - } - } else { - // Should not happen - Toast.makeText(this, R.string.no_message_seletected_toast, Toast.LENGTH_SHORT).show(); - } - - mAdapter.sortMessages(); +// boolean newState = false; +// List messageList = new ArrayList(); +// List removeHolderList = new ArrayList(); +// +// if (v == mBatchDoneButton) { +// setAllSelected(false); +// return; +// } +// +// if (v == mBatchFlagButton) { +// newState = computeBatchDirection(true); +// } else { +// newState = computeBatchDirection(false); +// } +// +// if (v == mBatchArchiveButton) { +// final List selection = getSelectionFromCheckboxes(); +// onArchive(selection); +// return; +// } +// +// if (v == mBatchMoveButton) { +// final List selection = getSelectionFromCheckboxes(); +// onMove(selection); +// return; +// } +// +// synchronized (mAdapter.messages) { +// for (MessageInfoHolder holder : mAdapter.messages) { +// if (holder.selected) { +// if (v == mBatchDeleteButton) { +// removeHolderList.add(holder); +// } else if (v == mBatchFlagButton) { +// holder.flagged = newState; +// } else if (v == mBatchReadButton) { +// holder.read = newState; +// } +// messageList.add(holder.message); +// } +// } +// } +// mAdapter.removeMessages(removeHolderList); +// +// if (!messageList.isEmpty()) { +// if (v == mBatchDeleteButton) { +// mController.deleteMessages(messageList.toArray(EMPTY_MESSAGE_ARRAY), null); +// mSelectedCount = 0; +// toggleBatchButtons(); +// } else { +// mController.setFlag(messageList.toArray(EMPTY_MESSAGE_ARRAY), (v == mBatchReadButton ? Flag.SEEN : Flag.FLAGGED), newState); +// } +// } else { +// // Should not happen +// Toast.makeText(this, R.string.no_message_seletected_toast, Toast.LENGTH_SHORT).show(); +// } +// mHandler.sortMessages(); } @Override diff --git a/src/com/fsck/k9/activity/misc/SwipeGestureDetector.java b/src/com/fsck/k9/activity/misc/SwipeGestureDetector.java new file mode 100644 index 000000000..f020e41fc --- /dev/null +++ b/src/com/fsck/k9/activity/misc/SwipeGestureDetector.java @@ -0,0 +1,87 @@ +package com.fsck.k9.activity.misc; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.GestureDetector.SimpleOnGestureListener; + + +public class SwipeGestureDetector extends SimpleOnGestureListener { + private static final float SWIPE_MAX_OFF_PATH_DIP = 250f; + private static final float SWIPE_THRESHOLD_VELOCITY_DIP = 325f; + + private final OnSwipeGestureListener mListener; + private int mMinVelocity; + private int mMaxOffPath; + private MotionEvent mLastOnDownEvent = null; + + public SwipeGestureDetector(Context context, OnSwipeGestureListener listener) { + super(); + + if (listener == null) { + throw new IllegalArgumentException("'listener' may not be null"); + } + + mListener = listener; + + // Calculate the minimum distance required for this to count as a swipe. + // Convert the constant dips to pixels. + float gestureScale = context.getResources().getDisplayMetrics().density; + mMinVelocity = (int) (SWIPE_THRESHOLD_VELOCITY_DIP * gestureScale + 0.5f); + mMaxOffPath = (int) (SWIPE_MAX_OFF_PATH_DIP * gestureScale + 0.5f); + } + + @Override + public boolean onDown(MotionEvent e) { + mLastOnDownEvent = e; + return super.onDown(e); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + // Apparently sometimes e1 is null + // Found a workaround here: http://stackoverflow.com/questions/4151385/ + if (e1 == null) { + e1 = mLastOnDownEvent; + } + + // Make sure we avoid NullPointerExceptions + if (e1 == null || e2 == null) { + return false; + } + + // Calculate how much was actually swiped. + final float deltaX = e2.getX() - e1.getX(); + final float deltaY = e2.getY() - e1.getY(); + + // Calculate the minimum distance required for this to be considered a swipe. + final int minDistance = (int) Math.abs(deltaY * 4); + + try { + if (Math.abs(deltaY) > mMaxOffPath || Math.abs(velocityX) < mMinVelocity) { + return false; + } + + if (deltaX < (minDistance * -1)) { + mListener.onSwipeRightToLeft(e1, e2); + } else if (deltaX > minDistance) { + mListener.onSwipeLeftToRight(e1, e2); + } else { + return false; + } + + // successful fling, cancel the 2nd event to prevent any other action from happening + // see http://code.google.com/p/android/issues/detail?id=8497 + e2.setAction(MotionEvent.ACTION_CANCEL); + } catch (Exception e) { + // nothing + } + + return false; + } + + + public interface OnSwipeGestureListener { + void onSwipeRightToLeft(final MotionEvent e1, final MotionEvent e2); + void onSwipeLeftToRight(final MotionEvent e1, final MotionEvent e2); + } +}