diff options
15 files changed, 810 insertions, 22 deletions
diff --git a/res/layout/bookmarks.xml b/res/layout/bookmarks.xml index 5ae022e1..f2a0854a 100644 --- a/res/layout/bookmarks.xml +++ b/res/layout/bookmarks.xml @@ -17,7 +17,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <ListView + <com.cyanogenmod.filemanager.ui.widgets.FlingerListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/bookmarks_listview" android:layout_width="match_parent" diff --git a/res/layout/navigation_view_details.xml b/res/layout/navigation_view_details.xml index d66977fb..b3c078f3 100644 --- a/res/layout/navigation_view_details.xml +++ b/res/layout/navigation_view_details.xml @@ -13,7 +13,8 @@ ** 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.
--> -<ListView xmlns:android="http://schemas.android.com/apk/res/android" +<com.cyanogenmod.filemanager.ui.widgets.FlingerListView + xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/navigation_view_layout" android:layout_width="match_parent" android:layout_height="match_parent" /> diff --git a/res/layout/navigation_view_simple.xml b/res/layout/navigation_view_simple.xml index d66977fb..b3c078f3 100644 --- a/res/layout/navigation_view_simple.xml +++ b/res/layout/navigation_view_simple.xml @@ -13,7 +13,8 @@ ** 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.
--> -<ListView xmlns:android="http://schemas.android.com/apk/res/android" +<com.cyanogenmod.filemanager.ui.widgets.FlingerListView + xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/navigation_view_layout" android:layout_width="match_parent" android:layout_height="match_parent" /> diff --git a/res/layout/search.xml b/res/layout/search.xml index 6e6863cd..befc89e6 100644 --- a/res/layout/search.xml +++ b/res/layout/search.xml @@ -68,7 +68,7 @@ android:textAppearance="@style/secondary_text_appearance" /> </RelativeLayout> - <ListView + <com.cyanogenmod.filemanager.ui.widgets.FlingerListView android:id="@+id/search_listview" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/res/values/strings.xml b/res/values/strings.xml index 93cd3b99..85c00ad8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -630,7 +630,11 @@ <!-- Preferences * General * Compute folder statistics summary on --> <string name="pref_compute_folder_statistics_on">Warning! The computation of folder statistics is costly in time and system resources</string> - <!-- Preferences * General * Advanced settings category --> + <!-- Preferences * General * Use flinger detection --> + <string name="pref_use_flinger">Use swipe gestures</string> + <!-- Preferences * General * Use flinger detection summary --> + <string name="pref_use_flinger_summary">Use swipe left to right gesture detection to delete files or folders.</string> + <!-- Preferences * General * Advanced settings category --> <string name="pref_general_advanced_settings_category">Advanced</string> <!-- Preferences * General * Access mode --> <string name="pref_access_mode">Access mode</string> diff --git a/res/xml/preferences_general.xml b/res/xml/preferences_general.xml index e195e187..1a286a50 100644 --- a/res/xml/preferences_general.xml +++ b/res/xml/preferences_general.xml @@ -46,6 +46,14 @@ android:persistent="true" android:defaultValue="false" /> + <!-- Use flinger --> + <CheckBoxPreference + android:key="cm_filemanager_use_flinger" + android:title="@string/pref_use_flinger" + android:summary="@string/pref_use_flinger_summary" + android:persistent="true" + android:defaultValue="true" /> + </PreferenceCategory> <!-- Advanced settings --> diff --git a/src/com/cyanogenmod/filemanager/activities/BookmarksActivity.java b/src/com/cyanogenmod/filemanager/activities/BookmarksActivity.java index 84880dd6..c2d1b1d5 100644 --- a/src/com/cyanogenmod/filemanager/activities/BookmarksActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/BookmarksActivity.java @@ -49,6 +49,9 @@ import com.cyanogenmod.filemanager.preferences.Bookmarks; import com.cyanogenmod.filemanager.preferences.FileManagerSettings; import com.cyanogenmod.filemanager.preferences.Preferences; import com.cyanogenmod.filemanager.ui.dialogs.InitialDirectoryDialog; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerListener; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder; import com.cyanogenmod.filemanager.util.CommandHelper; import com.cyanogenmod.filemanager.util.DialogHelper; import com.cyanogenmod.filemanager.util.ExceptionUtil; @@ -67,6 +70,62 @@ public class BookmarksActivity extends Activity implements OnItemClickListener, private static boolean DEBUG = false; + /** + * A listener for flinging events from {@link FlingerListView} + */ + private final OnItemFlingerListener mOnItemFlingerListener = new OnItemFlingerListener() { + + @Override + public boolean onItemFlingerStart( + AdapterView<?> parent, View view, int position, long id) { + try { + // Response if the item can be removed + BookmarksAdapter adapter = (BookmarksAdapter)parent.getAdapter(); + Bookmark bookmark = adapter.getItem(position); + if (bookmark != null && + bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) { + return true; + } + } catch (Exception e) { + ExceptionUtil.translateException(BookmarksActivity.this, e, true, false); + } + return false; + } + + @Override + public void onItemFlingerEnd(OnItemFlingerResponder responder, + AdapterView<?> parent, View view, int position, long id) { + + try { + // Response if the item can be removed + BookmarksAdapter adapter = (BookmarksAdapter)parent.getAdapter(); + Bookmark bookmark = adapter.getItem(position); + if (bookmark != null && + bookmark.mType.compareTo(BOOKMARK_TYPE.USER_DEFINED) == 0) { + boolean result = Bookmarks.removeBookmark(BookmarksActivity.this, bookmark); + if (!result) { + //Show warning + DialogHelper.showToast(BookmarksActivity.this, + R.string.msgs_operation_failure, Toast.LENGTH_SHORT); + responder.cancel(); + return; + } + responder.accept(); + adapter.remove(bookmark); + adapter.notifyDataSetChanged(); + return; + } + + // Cancels the flinger operation + responder.cancel(); + + } catch (Exception e) { + ExceptionUtil.translateException(BookmarksActivity.this, e, true, false); + responder.cancel(); + } + } + }; + // Bookmark list XML tags private static final String TAG_BOOKMARKS = "Bookmarks"; //$NON-NLS-1$ private static final String TAG_BOOKMARK = "bookmark"; //$NON-NLS-1$ @@ -143,6 +202,20 @@ public class BookmarksActivity extends Activity implements OnItemClickListener, BookmarksAdapter adapter = new BookmarksAdapter(this, bookmarks, this); this.mBookmarksListView.setAdapter(adapter); this.mBookmarksListView.setOnItemClickListener(this); + + // If we should set the listview to response to flinger gesture detection + boolean useFlinger = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_USE_FLINGER.getId(), + ((Boolean)FileManagerSettings. + SETTINGS_USE_FLINGER. + getDefaultValue()).booleanValue()); + if (useFlinger) { + ((FlingerListView)this.mBookmarksListView). + setOnItemFlingerListener(this.mOnItemFlingerListener); + } + + // Reload the data refresh(); } @@ -243,9 +316,9 @@ public class BookmarksActivity extends Activity implements OnItemClickListener, @Override public void onClick(View v) { //Retrieve the position - int position = ((Integer)v.getTag()).intValue(); - BookmarksAdapter adapter = (BookmarksAdapter)this.mBookmarksListView.getAdapter(); - Bookmark bookmark = adapter.getItem(position); + final int position = ((Integer)v.getTag()).intValue(); + final BookmarksAdapter adapter = (BookmarksAdapter)this.mBookmarksListView.getAdapter(); + final Bookmark bookmark = adapter.getItem(position); //Configure home if (bookmark.mType.compareTo(BOOKMARK_TYPE.HOME) == 0) { @@ -254,7 +327,8 @@ public class BookmarksActivity extends Activity implements OnItemClickListener, dialog.setOnValueChangedListener(new InitialDirectoryDialog.OnValueChangedListener() { @Override public void onValueChanged(String newInitialDir) { - refresh(); + adapter.getItem(position).mPath = newInitialDir; + adapter.notifyDataSetChanged(); } }); dialog.show(); @@ -269,7 +343,8 @@ public class BookmarksActivity extends Activity implements OnItemClickListener, DialogHelper.showToast(this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT); return; } - refresh(); + adapter.remove(bookmark); + adapter.notifyDataSetChanged(); return; } } diff --git a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java index a25c650c..ad402c3c 100644 --- a/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/NavigationActivity.java @@ -195,6 +195,19 @@ public class NavigationActivity extends Activity return; } + // Use flinger + if (key.compareTo(FileManagerSettings. + SETTINGS_USE_FLINGER.getId()) == 0) { + boolean useFlinger = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_USE_FLINGER.getId(), + ((Boolean)FileManagerSettings. + SETTINGS_USE_FLINGER. + getDefaultValue()).booleanValue()); + getCurrentNavigationView().setUseFlinger(useFlinger); + return; + } + // Access mode if (key.compareTo(FileManagerSettings. SETTINGS_ACCESS_MODE.getId()) == 0) { @@ -862,6 +875,8 @@ public class NavigationActivity extends Activity //Remove from history removeFromHistory((FileSystemObject)o); + } else { + onRequestRefresh(null); } this.getCurrentNavigationView().onDeselectAll(); } diff --git a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java index 8a6161fa..0375b997 100644 --- a/src/com/cyanogenmod/filemanager/activities/SearchActivity.java +++ b/src/com/cyanogenmod/filemanager/activities/SearchActivity.java @@ -56,6 +56,7 @@ import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; import com.cyanogenmod.filemanager.model.Directory; import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.model.ParentDirectory; import com.cyanogenmod.filemanager.model.Query; import com.cyanogenmod.filemanager.model.SearchResult; import com.cyanogenmod.filemanager.model.Symlink; @@ -67,8 +68,12 @@ import com.cyanogenmod.filemanager.providers.RecentSearchesContentProvider; import com.cyanogenmod.filemanager.tasks.SearchResultDrawingAsyncTask; import com.cyanogenmod.filemanager.ui.dialogs.ActionsDialog; import com.cyanogenmod.filemanager.ui.dialogs.MessageProgressDialog; +import com.cyanogenmod.filemanager.ui.policy.DeleteActionPolicy; import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy; import com.cyanogenmod.filemanager.ui.widgets.ButtonItem; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerListener; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder; import com.cyanogenmod.filemanager.util.CommandHelper; import com.cyanogenmod.filemanager.util.DialogHelper; import com.cyanogenmod.filemanager.util.ExceptionUtil; @@ -139,6 +144,59 @@ public class SearchActivity extends Activity }; /** + * A listener for flinging events from {@link FlingerListView} + */ + private final OnItemFlingerListener mOnItemFlingerListener = new OnItemFlingerListener() { + + @Override + public boolean onItemFlingerStart( + AdapterView<?> parent, View view, int position, long id) { + try { + // Response if the item can be removed + SearchResultAdapter adapter = (SearchResultAdapter)parent.getAdapter(); + SearchResult result = adapter.getItem(position); + if (result != null && result.getFso() != null) { + if (result.getFso() instanceof ParentDirectory) { + // This is not possible ... + return false; + } + return true; + } + } catch (Exception e) { + ExceptionUtil.translateException(SearchActivity.this, e, true, false); + } + return false; + } + + @Override + public void onItemFlingerEnd(OnItemFlingerResponder responder, + AdapterView<?> parent, View view, int position, long id) { + + try { + // Response if the item can be removed + SearchResultAdapter adapter = (SearchResultAdapter)parent.getAdapter(); + SearchResult result = adapter.getItem(position); + if (result != null && result.getFso() != null) { + DeleteActionPolicy.removeFileSystemObject( + SearchActivity.this, + result.getFso(), + null, + SearchActivity.this, + responder); + return; + } + + // Cancels the flinger operation + responder.cancel(); + + } catch (Exception e) { + ExceptionUtil.translateException(SearchActivity.this, e, true, false); + responder.cancel(); + } + } + }; + + /** * @hide */ MessageProgressDialog mDialog = null; @@ -345,6 +403,18 @@ public class SearchActivity extends Activity this.mSearchListView.setOnItemClickListener(this); this.mSearchListView.setOnItemLongClickListener(this); + // If we should set the listview to response to flinger gesture detection + boolean useFlinger = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_USE_FLINGER.getId(), + ((Boolean)FileManagerSettings. + SETTINGS_USE_FLINGER. + getDefaultValue()).booleanValue()); + if (useFlinger) { + ((FlingerListView)this.mSearchListView). + setOnItemFlingerListener(this.mOnItemFlingerListener); + } + //Other components this.mSearchWaiting = (ProgressBar)findViewById(R.id.search_waiting); this.mSearchFoundItems = (TextView)findViewById(R.id.search_status_found_items); diff --git a/src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java b/src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java index b253b679..53dc9d49 100644 --- a/src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java +++ b/src/com/cyanogenmod/filemanager/activities/preferences/SettingsPreferences.java @@ -123,6 +123,7 @@ public class SettingsPreferences extends PreferenceActivity { private CheckBoxPreference mCaseSensitiveSort; private ListPreference mFreeDiskSpaceWarningLevel; private CheckBoxPreference mComputeFolderStatistics; + private CheckBoxPreference mUseFlinger; private ListPreference mAccessMode; private CheckBoxPreference mDebugTraces; @@ -235,6 +236,12 @@ public class SettingsPreferences extends PreferenceActivity { FileManagerSettings.SETTINGS_COMPUTE_FOLDER_STATISTICS.getId()); this.mComputeFolderStatistics.setOnPreferenceChangeListener(this.mOnChangeListener); + // Use flinger + this.mUseFlinger = + (CheckBoxPreference)findPreference( + FileManagerSettings.SETTINGS_USE_FLINGER.getId()); + this.mUseFlinger.setOnPreferenceChangeListener(this.mOnChangeListener); + // Access mode this.mAccessMode = (ListPreference)findPreference( diff --git a/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java b/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java index a3188734..ca4620bc 100644 --- a/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java +++ b/src/com/cyanogenmod/filemanager/preferences/FileManagerSettings.java @@ -91,6 +91,11 @@ public enum FileManagerSettings { */ SETTINGS_COMPUTE_FOLDER_STATISTICS( "cm_filemanager_compute_folder_statistics", Boolean.FALSE), //$NON-NLS-1$ + /** + * Whether use flinger to remove items + * @hide + */ + SETTINGS_USE_FLINGER("cm_filemanager_use_flinger", Boolean.TRUE), //$NON-NLS-1$ /** * When to highlight the terms of the search in the search results diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java index 7bc7ed08..0e193433 100644 --- a/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java +++ b/src/com/cyanogenmod/filemanager/ui/dialogs/ActionsDialog.java @@ -234,7 +234,8 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen this.mContext, this.mFso, this.mOnSelectionListener, - this.mOnRequestRefreshListener); + this.mOnRequestRefreshListener, + null); break; //- Refresh @@ -318,7 +319,8 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen this.mContext, selection, this.mOnSelectionListener, - this.mOnRequestRefreshListener); + this.mOnRequestRefreshListener, + null); } break; diff --git a/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java index a7c38922..238e84f8 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java @@ -28,6 +28,7 @@ import com.cyanogenmod.filemanager.console.RelaunchableException; import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; import com.cyanogenmod.filemanager.listeners.OnSelectionListener; import com.cyanogenmod.filemanager.model.FileSystemObject; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder; import com.cyanogenmod.filemanager.util.CommandHelper; import com.cyanogenmod.filemanager.util.DialogHelper; import com.cyanogenmod.filemanager.util.ExceptionUtil; @@ -52,15 +53,20 @@ public final class DeleteActionPolicy extends ActionsPolicy { * @param fso The file system object to remove * @param onSelectionListener The listener for obtain selection information (required) * @param onRequestRefreshListener The listener for request a refresh (optional) + * @param onItemFlingerResponder The flinger responder, only if the action was initialized + * by a flinger gesture (optional) */ public static void removeFileSystemObject( final Context ctx, final FileSystemObject fso, final OnSelectionListener onSelectionListener, - final OnRequestRefreshListener onRequestRefreshListener) { + final OnRequestRefreshListener onRequestRefreshListener, + final OnItemFlingerResponder onItemFlingerResponder) { // Generate an array and invoke internal method List<FileSystemObject> files = new ArrayList<FileSystemObject>(1); files.add(fso); - removeFileSystemObjects(ctx, files, onSelectionListener, onRequestRefreshListener); + removeFileSystemObjects( + ctx, files, onSelectionListener, + onRequestRefreshListener, onItemFlingerResponder); } /** @@ -70,11 +76,14 @@ public final class DeleteActionPolicy extends ActionsPolicy { * @param files The list of files to remove * @param onSelectionListener The listener for obtain selection information (required) * @param onRequestRefreshListener The listener for request a refresh (optional) + * @param onItemFlingerResponder The flinger responder, only if the action was initialized + * by a flinger gesture (optional) */ public static void removeFileSystemObjects( final Context ctx, final List<FileSystemObject> files, final OnSelectionListener onSelectionListener, - final OnRequestRefreshListener onRequestRefreshListener) { + final OnRequestRefreshListener onRequestRefreshListener, + final OnItemFlingerResponder onItemFlingerResponder) { // Ask the user before remove AlertDialog dialog = DialogHelper.createYesNoDialog( @@ -90,7 +99,13 @@ public final class DeleteActionPolicy extends ActionsPolicy { ctx, files, onSelectionListener, - onRequestRefreshListener); + onRequestRefreshListener, + onItemFlingerResponder); + } else { + // Flinger operation should be cancelled + if (onItemFlingerResponder != null) { + onItemFlingerResponder.cancel(); + } } } }); @@ -104,12 +119,15 @@ public final class DeleteActionPolicy extends ActionsPolicy { * @param files The list of files to remove * @param onSelectionListener The listener for obtain selection information (optional) * @param onRequestRefreshListener The listener for request a refresh (optional) + * @param onItemFlingerResponder The flinger responder, only if the action was initialized + * by a flinger gesture (optional) * @hide */ static void removeFileSystemObjectsInBackground( final Context ctx, final List<FileSystemObject> files, final OnSelectionListener onSelectionListener, - final OnRequestRefreshListener onRequestRefreshListener) { + final OnRequestRefreshListener onRequestRefreshListener, + final OnItemFlingerResponder onItemFlingerResponder) { // Some previous checks prior to execute // 1.- Check the operation consistency (only if it is viable) @@ -167,10 +185,21 @@ public final class DeleteActionPolicy extends ActionsPolicy { @Override public void onSuccess() { - //Operation complete. Refresh + //Operation complete. + + // Confirms flinger operation + if (onItemFlingerResponder != null) { + onItemFlingerResponder.accept(); + } + + // Refresh if (this.mOnRequestRefreshListener != null) { - // The reference is not the same, so refresh the complete navigation view - this.mOnRequestRefreshListener.onRequestRefresh(null); + // The reference is not the same, so refresh the complete navigation view + if (files != null && files.size() == 1) { + this.mOnRequestRefreshListener.onRequestRemove(files.get(0)); + } else { + this.mOnRequestRefreshListener.onRequestRemove(null); + } } ActionsPolicy.showOperationSuccessMsg(ctx); } @@ -250,11 +279,21 @@ public final class DeleteActionPolicy extends ActionsPolicy { // Persist the exception? if (this.mCause != null) { + // Cancels the flinger + if (onItemFlingerResponder != null) { + onItemFlingerResponder.cancel(); + } + // The exception must be elevated throw this.mCause; } } else { + // Cancels the flinger + if (onItemFlingerResponder != null) { + onItemFlingerResponder.cancel(); + } + // The exception must be elevated throw e; } @@ -275,6 +314,11 @@ public final class DeleteActionPolicy extends ActionsPolicy { // Operation complete successfully } if (failed) { + // Cancels the flinger + if (onItemFlingerResponder != null) { + onItemFlingerResponder.cancel(); + } + throw new ExecutionException( String.format( "Failed to delete file: %s", fso.getFullPath())); //$NON-NLS-1$ diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java b/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java new file mode 100644 index 00000000..1fd5846f --- /dev/null +++ b/src/com/cyanogenmod/filemanager/ui/widgets/FlingerListView.java @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2012 The CyanogenMod Project + * + * 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.cyanogenmod.filemanager.ui.widgets; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.widget.AdapterView; +import android.widget.ListView; + +/** + * A {@link ListView} implementation for remove items using flinging gesture. + */ +public class FlingerListView extends ListView { + + /** + * An interface for dispatch flinging gestures + */ + public interface OnItemFlingerListener { + /** + * Method invoke when a row item is going to be flinging. + * + * @param parent The AbsListView where the flinging happened + * @param view The view within the AbsListView that was flingered + * @param position The position of the view in the list + * @param id The row id of the item that was flingered + * @return boolean If the flinging operation must continue + */ + boolean onItemFlingerStart(AdapterView<?> parent, View view, int position, long id); + + /** + * Method invoke when a row item was flingered. + * + * @param responder The responder to the flinging action. You MUST be invoke one + * the option methods of this interface (accept or cancel). + * @param parent The AbsListView where the flinging happened + * @param view The view within the AbsListView that was flingered + * @param position The position of the view in the list + * @param id The row id of the item that was flingered + */ + void onItemFlingerEnd( + OnItemFlingerResponder responder, + AdapterView<?> parent, View view, int position, long id); + } + + /** + * An interface for response to {@link OnItemFlingerListener#onItemFlingerEnd( + * OnItemFlingerResponder, AdapterView, View, int, long)} event. + */ + public interface OnItemFlingerResponder { + /** + * Method that indicates that the item was removed. This method MUST be called + * after the remove of item (that it's responsibility of the invoker) to ensure + * that all references are cleaned. + */ + void accept(); + + /** + * Method that indicates that the action must be cancelled, and the item + * MUST NOT be removed. + */ + void cancel(); + } + + /** + * An implementation of {@link OnItemFlingerResponder} + */ + private class ItemFlingerResponder implements OnItemFlingerResponder { + /** + * @hide + */ + View mItemView; + + /** + * Constructor of <code></code>. For synthetic-access only. + */ + public ItemFlingerResponder() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public void accept() { + // Remove the flinger effect + this.mItemView.setTranslationX(0); + clearVars(); + } + + /** + * {@inheritDoc} + */ + @Override + public void cancel() { + // Remove the flinger effect + this.mItemView.setTranslationX(0); + clearVars(); + } + } + + /** + * The time after that the pressed is sending to the view. + */ + private static final long PRESSED_DELAY_TIME = 250L; + + /** + * The default percentage for flinging remove event. + */ + private static final float DEFAULT_FLING_REMOVE_PERCENTAJE = 0.60f; + + // Flinging data + private int mTranslationX = 0; + private int mStartX = 0; + private int mStartY = 0; + private int mCurrentX = 0; + private int mCurrentY = 0; + private int mFlingingViewPos; + private View mFlingingView; + private boolean mFlingingViewPressed; + private int mFlingingViewHeight; + private int mFlingingViewWidth; + private boolean mScrolling; + private boolean mFlinging; + private boolean mFlingingStarted; + private boolean mMoveStarted; + private boolean mLongPress; + private Runnable mLongPressDetection; + + private float mFlingRemovePercentaje; + private OnItemFlingerListener mOnItemFlingerListener; + + /** + * Constructor of <code>FlingerListView</code>. + * + * @param context The current context + */ + public FlingerListView(Context context) { + super(context); + init(); + } + + /** + * Constructor of <code>FlingerListView</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public FlingerListView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * Constructor of <code>FlingerListView</code>. + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public FlingerListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * Method that initializes the view + */ + private void init() { + //Initialize variables + this.mFlingRemovePercentaje = DEFAULT_FLING_REMOVE_PERCENTAJE; + } + + /** + * Method that returns the percentage (from 0 to 1) of the item view width on which + * an OnItemFlinger event occurs + * + * @return float The percentage (from 0 to 1) of the item view width + */ + public float getFlingRemovePercentaje() { + return this.mFlingRemovePercentaje; + } + + /** + * Method that sets the percentage (from 0 to 1) of the item view width on which + * an OnItemFlinger event occurs + * + * @param flingRemovePercentaje The percentage (from 0 to 1) of the item view width + */ + public void setFlingRemovePercentaje(float flingRemovePercentaje) { + if (flingRemovePercentaje < 0) { + this.mFlingRemovePercentaje = 0; + } else if (flingRemovePercentaje > 1) { + this.mFlingRemovePercentaje = 1; + } else { + this.mFlingRemovePercentaje = flingRemovePercentaje; + } + } + + /** + * Method that sets the listener for listen flinging events + * + * @param mOnItemFlingerListener The flinging listener + */ + public void setOnItemFlingerListener(OnItemFlingerListener mOnItemFlingerListener) { + this.mOnItemFlingerListener = mOnItemFlingerListener; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Get information about the x and y + int x = (int) ev.getX(); + int y = (int) ev.getY(); + + // Detect the motion + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + // Clean variables + this.mScrolling = false; + this.mFlinging = false; + this.mLongPress = false; + this.mFlingingStarted = false; + this.mMoveStarted = false; + if (this.mFlingingView != null) { + this.mFlingingView.setTranslationX(0); + } + + // Get the view to fling + this.mFlingingViewPos = pointToPosition(x, y); + if (this.mFlingingViewPos != INVALID_POSITION) { + this.mStartX = (int) ev.getX(); + this.mCurrentX = (int) ev.getX(); + this.mStartY = (int) ev.getY(); + this.mCurrentY = (int) ev.getY(); + this.mTranslationX = 0; + this.mFlingingView = + getChildAt(this.mFlingingViewPos - getFirstVisiblePosition()); + this.mFlingingViewPressed = true; + + // Detect long press event + if (getOnItemLongClickListener() != null) { + this.mLongPressDetection = new Runnable() { + @Override + @SuppressWarnings({"synthetic-access" }) + public void run() { + if (!FlingerListView.this.mFlingingStarted && + !FlingerListView.this.mMoveStarted) { + // Notify the long-click + FlingerListView.this.mLongPress = true; + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + FlingerListView.this.mFlingingView.setPressed(false); + getOnItemLongClickListener().onItemLongClick( + FlingerListView.this, + FlingerListView.this.mFlingingView, + FlingerListView.this.mFlingingViewPos, + FlingerListView.this.mFlingingView.getId()); + } + } + }; + this.mFlingingView.postDelayed( + this.mLongPressDetection, + ViewConfiguration.getLongPressTimeout()); + } + + // Calculate the item size + Rect r = new Rect(); + this.mFlingingView.getDrawingRect(r); + this.mFlingingViewWidth = r.width(); + this.mFlingingViewHeight = r.height(); + + // Set the pressed state + this.mFlingingView.postDelayed(new Runnable() { + @Override + @SuppressWarnings("synthetic-access") + public void run() { + if (FlingerListView.this.mFlingingViewPressed) { + FlingerListView.this.mFlingingView.setPressed(true); + } + } + }, PRESSED_DELAY_TIME); + + return true; + } + break; + + case MotionEvent.ACTION_MOVE: + this.mMoveStarted = true; + if (this.mFlingingView != null) { + this.mFlingingView.removeCallbacks(this.mLongPressDetection); + this.mFlingingViewPressed = false; + FlingerListView.this.mFlingingView.setPressed(false); + } + + // With flinging support + if (this.mFlingingView != null && this.mOnItemFlingerListener != null) { + // Detect scrolling + this.mCurrentY = (int)ev.getY(); + this.mScrolling = + Math.abs(this.mCurrentY - this.mStartY) > this.mFlingingViewHeight; + + + // Only if event has changed (and only to the right and if not scrolling) + if (!this.mScrolling) { + if (ev.getX() >= this.mStartX && (ev.getX() - this.mCurrentX != 0)) { + // Started + if (!this.mFlingingStarted) { + // Flinging starting + if (!this.mOnItemFlingerListener.onItemFlingerStart( + this, + this.mFlingingView, + this.mFlingingViewPos, + this.mFlingingView.getId())) { + break; + } + this.mFlingingStarted = true; + } + + + this.mCurrentX = (int)ev.getX(); + this.mTranslationX = this.mCurrentX - this.mStartX; + this.mFlingingView.setTranslationX(this.mTranslationX); + this.mFlingingView.setPressed(false); + + // Detect if flinging occurs + float flingLimit = + (this.mFlingingViewWidth * this.mFlingRemovePercentaje); + if (!this.mFlinging && this.mTranslationX > flingLimit) { + // Flinging occurs. Mark and raise an event + this.mFlinging = true; + final ItemFlingerResponder responder = + new ItemFlingerResponder(); + responder.mItemView = this.mFlingingView; + + // Request a response (we need to do this in background for + // get new events) + this.mFlingingView.post(new Runnable() { + @Override + @SuppressWarnings("synthetic-access") + public void run() { + FlingerListView. + this.mOnItemFlingerListener.onItemFlingerEnd( + responder, + FlingerListView.this, + FlingerListView.this.mFlingingView, + FlingerListView.this.mFlingingViewPos, + FlingerListView.this.mFlingingView.getId()); + } + }); + } + } + } + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + // Clear flinging (only if not waiting confirmation) + // On scrolling, flinging has no effect + if (!this.mFlinging || this.mScrolling) { + this.mStartX = 0; + this.mCurrentX = 0; + this.mTranslationX = 0; + if (this.mFlingingView != null) { + this.mFlingingView.setTranslationX(0); + } + } else { + // Force to display at the limit + if (this.mFlingingView != null) { + float flingLimit = + (this.mFlingingViewWidth * this.mFlingRemovePercentaje); + this.mFlingingView.setTranslationX(flingLimit); + } + } + + // What is the motion + if (!this.mScrolling && this.mFlingingView != null && + this.mOnItemFlingerListener != null) { + if (!this.mMoveStarted) { + if (!this.mLongPress) { + this.mFlingingView.removeCallbacks(this.mLongPressDetection); + this.mFlingingView.setPressed(true); + this.mFlingingView.postDelayed(new Runnable() { + @Override + @SuppressWarnings("synthetic-access") + public void run() { + FlingerListView.this.mFlingingView.setPressed(false); + } + }, PRESSED_DELAY_TIME); + performItemClick( + this.mFlingingView, + this.mFlingingViewPos, + this.mFlingingView.getId()); + } + } + + // Handled + return true; + } + + // Scrolling -> Remove any status (don't handle event) + if (this.mFlingingView != null) { + this.mFlingingView.setPressed(false); + } + break; + + default: + break; + } + + return super.onTouchEvent(ev); + } + + /** + * Method that clean the internal variables + * @hide + */ + void clearVars() { + this.mScrolling = false; + this.mFlinging = false; + this.mLongPress = false; + this.mFlingingStarted = false; + this.mMoveStarted = false; + this.mFlingingView = null; + } +} diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java index dccbd0f7..2533944f 100644 --- a/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java +++ b/src/com/cyanogenmod/filemanager/ui/widgets/NavigationView.java @@ -49,7 +49,10 @@ import com.cyanogenmod.filemanager.preferences.FileManagerSettings; import com.cyanogenmod.filemanager.preferences.NavigationLayoutMode; import com.cyanogenmod.filemanager.preferences.ObjectIdentifier; import com.cyanogenmod.filemanager.preferences.Preferences; +import com.cyanogenmod.filemanager.ui.policy.DeleteActionPolicy; import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerListener; +import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder; import com.cyanogenmod.filemanager.util.CommandHelper; import com.cyanogenmod.filemanager.util.DialogHelper; import com.cyanogenmod.filemanager.util.ExceptionUtil; @@ -69,6 +72,8 @@ public class NavigationView extends RelativeLayout implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener, BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRefreshListener { + private static final String TAG = "NavigationView"; //$NON-NLS-1$ + /** * An interface to communicate selection changes events. */ @@ -124,7 +129,57 @@ public class NavigationView extends RelativeLayout implements PICKABLE, } - private static final String TAG = "NavigationView"; //$NON-NLS-1$ + /** + * A listener for flinging events from {@link FlingerListView} + */ + private final OnItemFlingerListener mOnItemFlingerListener = new OnItemFlingerListener() { + + @Override + public boolean onItemFlingerStart( + AdapterView<?> parent, View view, int position, long id) { + try { + // Response if the item can be removed + FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)parent.getAdapter(); + FileSystemObject fso = adapter.getItem(position); + if (fso != null) { + if (fso instanceof ParentDirectory) { + return false; + } + return true; + } + } catch (Exception e) { + ExceptionUtil.translateException(getContext(), e, true, false); + } + return false; + } + + @Override + public void onItemFlingerEnd(OnItemFlingerResponder responder, + AdapterView<?> parent, View view, int position, long id) { + + try { + // Response if the item can be removed + FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)parent.getAdapter(); + FileSystemObject fso = adapter.getItem(position); + if (fso != null) { + DeleteActionPolicy.removeFileSystemObject( + getContext(), + fso, + NavigationView.this, + NavigationView.this, + responder); + return; + } + + // Cancels the flinger operation + responder.cancel(); + + } catch (Exception e) { + ExceptionUtil.translateException(getContext(), e, true, false); + responder.cancel(); + } + } + }; private int mId; private String mCurrentDir; @@ -413,6 +468,29 @@ public class NavigationView extends RelativeLayout implements } /** + * Method that sets if the view should use flinger gesture detection. + * + * @param useFlinger If the view should use flinger gesture detection + */ + public void setUseFlinger(boolean useFlinger) { + if (this.mCurrentMode.compareTo(NavigationLayoutMode.ICONS) == 0) { + // Not supported + return; + } + // Set the flinger listener (only when navigate) + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + if (this.mAdapterView instanceof FlingerListView) { + if (useFlinger) { + ((FlingerListView)this.mAdapterView). + setOnItemFlingerListener(this.mOnItemFlingerListener); + } else { + ((FlingerListView)this.mAdapterView).setOnItemFlingerListener(null); + } + } + } + } + + /** * Method that forces the view to scroll to the file system object passed. * * @param fso The file system object @@ -425,6 +503,8 @@ public class NavigationView extends RelativeLayout implements } catch (Exception e) { this.mAdapterView.setSelection(0); } + } else { + this.mAdapterView.setSelection(0); } } @@ -471,6 +551,14 @@ public class NavigationView extends RelativeLayout implements return; } + // If we should set the listview to response to flinger gesture detection + boolean useFlinger = + Preferences.getSharedPreferences().getBoolean( + FileManagerSettings.SETTINGS_USE_FLINGER.getId(), + ((Boolean)FileManagerSettings. + SETTINGS_USE_FLINGER. + getDefaultValue()).booleanValue()); + //Creates the new layout AdapterView<ListAdapter> newView = null; int itemResourceId = -1; @@ -478,14 +566,32 @@ public class NavigationView extends RelativeLayout implements newView = (AdapterView<ListAdapter>)inflate( getContext(), RESOURCE_MODE_ICONS_LAYOUT, null); itemResourceId = RESOURCE_MODE_ICONS_ITEM; + } else if (newMode.compareTo(NavigationLayoutMode.SIMPLE) == 0) { newView = (AdapterView<ListAdapter>)inflate( getContext(), RESOURCE_MODE_SIMPLE_LAYOUT, null); itemResourceId = RESOURCE_MODE_SIMPLE_ITEM; + + // Set the flinger listener (only when navigate) + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + if (useFlinger && newView instanceof FlingerListView) { + ((FlingerListView)newView). + setOnItemFlingerListener(this.mOnItemFlingerListener); + } + } + } else if (newMode.compareTo(NavigationLayoutMode.DETAILS) == 0) { newView = (AdapterView<ListAdapter>)inflate( getContext(), RESOURCE_MODE_DETAILS_LAYOUT, null); itemResourceId = RESOURCE_MODE_DETAILS_ITEM; + + // Set the flinger listener (only when navigate) + if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) { + if (useFlinger && newView instanceof FlingerListView) { + ((FlingerListView)newView). + setOnItemFlingerListener(this.mOnItemFlingerListener); + } + } } //Get the current adapter and its adapter list @@ -917,6 +1023,8 @@ public class NavigationView extends RelativeLayout implements public void onRequestRefresh(Object o) { if (o instanceof FileSystemObject) { refresh((FileSystemObject)o); + } else if (o == null) { + refresh(); } onDeselectAll(); } @@ -926,8 +1034,10 @@ public class NavigationView extends RelativeLayout implements */ @Override public void onRequestRemove(Object o) { - if (o instanceof FileSystemObject) { + if (o != null && o instanceof FileSystemObject) { removeItem((FileSystemObject)o); + } else { + onRequestRefresh(null); } onDeselectAll(); } |