瀏覽代碼

Finish action mode when drawer starts to open, recover it when drawer is closed

# Conflicts:
#	build.gradle
#	src/com/owncloud/android/ui/activity/DrawerActivity.java
#	src/com/owncloud/android/ui/fragment/OCFileListFragment.java
David A. Velasco 8 年之前
父節點
當前提交
5a97d31392

+ 4 - 2
build.gradle

@@ -41,7 +41,6 @@ dependencies {
     compile "com.android.support:design:${supportLibraryVersion}"
     compile 'com.jakewharton:disklrucache:2.0.2'
     compile "com.android.support:appcompat-v7:${supportLibraryVersion}"
-    compile "com.android.support:cardview-v7:${supportLibraryVersion}"
     compile 'com.getbase:floatingactionbutton:1.10.1'
 
     /// dependencies for local unit tests
@@ -62,7 +61,10 @@ dependencies {
     androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
 
     // UIAutomator - for cross-app UI tests, and to grant screen is turned on in Espresso tests
-    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
+    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
+
+    // fix conflict in dependencies; see http://g.co/androidstudio/app-test-app-conflict for details
+    androidTestCompile "com.android.support:support-annotations:${supportLibraryVersion}"
 
 }
 

+ 14 - 1
src/com/owncloud/android/ui/activity/DrawerActivity.java

@@ -198,7 +198,7 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
         };
 
         // Set the drawer toggle as the DrawerListener
-        mDrawerLayout.setDrawerListener(mDrawerToggle);
+        mDrawerLayout.addDrawerListener(mDrawerToggle);
         mDrawerToggle.setDrawerIndicatorEnabled(true);
     }
 
@@ -796,4 +796,17 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
         }
         return false;
     }
+
+    /**
+     * Adds other listeners to react on changes of the drawer layout.
+     *
+     * @param listener      Object interested in changes of the drawer layout.
+     */
+    public void addDrawerListener(DrawerLayout.DrawerListener listener) {
+        if (mDrawerLayout != null) {
+            mDrawerLayout.addDrawerListener(listener);
+        } else {
+            Log_OC.e(TAG, "Drawer layout not ready to add drawer listener");
+        }
+    }
 }

+ 178 - 53
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -28,7 +28,9 @@ import android.content.SharedPreferences;
 import android.os.Build;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
+import android.support.v4.widget.DrawerLayout;
 import android.support.v4.widget.SwipeRefreshLayout;
+import android.util.SparseBooleanArray;
 import android.view.ActionMode;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -60,6 +62,8 @@ import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
 import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
+import com.owncloud.android.ui.helpers.SparseBooleanArrayParcelable;
+import com.owncloud.android.ui.preview.PreviewAudioFragment;
 import com.owncloud.android.ui.preview.PreviewImageFragment;
 import com.owncloud.android.ui.preview.PreviewMediaFragment;
 import com.owncloud.android.ui.preview.PreviewTextFragment;
@@ -107,6 +111,7 @@ public class OCFileListFragment extends ExtendedListFragment {
     private boolean mHideFab = true;
     private boolean miniFabClicked = false;
     private ActionMode mActiveActionMode;
+    private OCFileListFragment.MultiChoiceModeListener mMultiChoiceModeListener;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -151,7 +156,7 @@ public class OCFileListFragment extends ExtendedListFragment {
         Bundle args = getArguments();
         boolean allowContextualActions = (args != null) && args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, false);
         if (allowContextualActions) {
-            setChoiceModeAsMultipleModal();
+            setChoiceModeAsMultipleModal(savedInstanceState);
         }
         Log_OC.i(TAG, "onCreateView() end");
         return v;
@@ -346,76 +351,195 @@ public class OCFileListFragment extends ExtendedListFragment {
                 com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE);
     }
 
-    private void setChoiceModeAsMultipleModal() {
+    /**
+     * Handler for multiple selection mode.
+     *
+     * Manages input from the user when one or more files or folders are selected in the list.
+     *
+     * Also listens to changes in navigation drawer to hide and recover multiple selection when it's opened
+     * and closed.
+     */
+    private class MultiChoiceModeListener
+        implements AbsListView.MultiChoiceModeListener, DrawerLayout.DrawerListener {
 
-        setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+        private static final String KEY_ACTION_MODE_CLOSED_BY_DRAWER = "KILLED_ACTION_MODE";
+        private static final String KEY_SELECTION_WHEN_CLOSED_BY_DRAWER = "CHECKED_ITEMS";
 
-        setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
+        /**
+         * True when action mode is finished because the drawer was opened
+         */
+        private boolean mActionModeClosedByDrawer = false;
 
-            @Override
-            public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
-                getListView().invalidateViews();
-                mode.invalidate();
-            }
+        /**
+         * Selected items in list when action mode is closed by drawer
+         */
+        private SparseBooleanArray mSelectionWhenActionModeClosedByDrawer = null;
 
-            @Override
-            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-                mActiveActionMode = mode;
+        @Override
+        public void onDrawerSlide(View drawerView, float slideOffset) {
+            // nothing to do
+        }
+
+        @Override
+        public void onDrawerOpened(View drawerView) {
+            // nothing to do
+        }
+
+        /**
+         * When the navigation drawer is closed, action mode is recovered in the same state as was
+         * when the drawer was (started to be) opened.
+         *
+         * @param drawerView        Navigation drawer just closed.
+         */
+        @Override
+        public void onDrawerClosed(View drawerView) {
+            if (mSelectionWhenActionModeClosedByDrawer !=null && mActionModeClosedByDrawer) {
+                for (int i = 0; i< mSelectionWhenActionModeClosedByDrawer.size(); i++) {
+                    if (mSelectionWhenActionModeClosedByDrawer.valueAt(i)) {
+                        getListView().setItemChecked(
+                            mSelectionWhenActionModeClosedByDrawer.keyAt(i),
+                            true
+                        );
+                    }
+                }
+            }
+            mSelectionWhenActionModeClosedByDrawer = null;
+        }
 
-                MenuInflater inflater = getActivity().getMenuInflater();
-                inflater.inflate(R.menu.file_actions_menu, menu);
-                mode.invalidate();
+        /**
+         * If the action mode is active when the navigation drawer starts to move, the action
+         * mode is closed and the selection stored to be recovered when the drawer is closed.
+         *
+         * @param newState     One of STATE_IDLE, STATE_DRAGGING or STATE_SETTLING.
+         */
+        @Override
+        public void onDrawerStateChanged(int newState) {
+            if (DrawerLayout.STATE_DRAGGING == newState && mActiveActionMode != null) {
+                mSelectionWhenActionModeClosedByDrawer = getListView().getCheckedItemPositions().clone();
+                mActiveActionMode.finish();
+                mActionModeClosedByDrawer = true;
+            }
+        }
 
-                //set gray color
-                DisplayUtils.colorStatusBar(getActivity(), mSystemBarActionModeColor);
-                DisplayUtils.colorToolbarProgressBar(getActivity(), mProgressBarActionModeColor);
 
-                // hide FAB in multi selection mode
-                setFabEnabled(false);
+        /**
+         * Update action mode bar when an item is selected / unselected in the list
+         */
+        @Override
+        public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+            getListView().invalidateViews();
+            mode.invalidate();
+        }
 
-                return true;
+        /**
+         * Load menu and customize UI when action mode is started.
+         */
+        @Override
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            mActiveActionMode = mode;
+
+            MenuInflater inflater = getActivity().getMenuInflater();
+            inflater.inflate(R.menu.file_actions_menu, menu);
+            mode.invalidate();
+
+            //set gray color
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                Window w = getActivity().getWindow();
+                mStatusBarColor = w.getStatusBarColor();
+                w.setStatusBarColor(mStatusBarColorActionMode);
             }
 
-            @Override
-            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-                List<OCFile> checkedFiles = mAdapter.getCheckedItems(getListView());
-                final int checkedCount = checkedFiles.size();
-                String title = getResources().getQuantityString(
-                    R.plurals.items_selected_count,
-                    checkedCount,
-                    checkedCount
-                );
-                mode.setTitle(title);
-                FileMenuFilter mf = new FileMenuFilter(
-                    checkedFiles,
-                    ((FileActivity) getActivity()).getAccount(),
-                    mContainerActivity,
-                    getActivity()
-                );
-                mf.filter(menu);
+            // hide FAB in multi selection mode
+            setFabEnabled(false);
 
-                return true;
+            return true;
+        }
+
+        /**
+         * Updates available action in menu depending on current selection.
+         */
+        @Override
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            List<OCFile> checkedFiles = mAdapter.getCheckedItems(getListView());
+            final int checkedCount = checkedFiles.size();
+            String title = getResources().getQuantityString(
+                R.plurals.items_selected_count,
+                checkedCount,
+                checkedCount
+            );
+            mode.setTitle(title);
+            FileMenuFilter mf = new FileMenuFilter(
+                checkedFiles,
+                ((FileActivity) getActivity()).getAccount(),
+                mContainerActivity,
+                getActivity()
+            );
+            mf.filter(menu);
+            return true;
+        }
+
+        /**
+         * Starts the corresponding action when a menu item is tapped by the user.
+         */
+        @Override
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return onFileActionChosen(item.getItemId());
+        }
+
+        /**
+         * Restores UI.
+         */
+        @Override
+        public void onDestroyActionMode(ActionMode mode) {
+            mActiveActionMode = null;
+
+            // reset to previous color
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                getActivity().getWindow().setStatusBarColor(mStatusBarColor);
             }
 
-            @Override
-            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-                return onFileActionChosen(item.getItemId());
+            // show FAB on multi selection mode exit
+            if(!mHideFab) {
+                setFabEnabled(true);
             }
+        }
 
-            @Override
-            public void onDestroyActionMode(ActionMode mode) {
-                mActiveActionMode = null;
 
-                // reset to previous color
-                DisplayUtils.colorStatusBar(getActivity(), mSystemBarColor);
-                DisplayUtils.colorToolbarProgressBar(getActivity(), mProgressBarColor);
+        public void storeStateIn(Bundle outState) {
+            outState.putBoolean(KEY_ACTION_MODE_CLOSED_BY_DRAWER, mActionModeClosedByDrawer);
+            if (mSelectionWhenActionModeClosedByDrawer != null) {
+                SparseBooleanArrayParcelable sbap = new SparseBooleanArrayParcelable(
+                    mSelectionWhenActionModeClosedByDrawer
+                );
+                outState.putParcelable(KEY_SELECTION_WHEN_CLOSED_BY_DRAWER, sbap);
+            }
+        }
 
-                // show FAB on multi selection mode exit
-                if(!mHideFab) {
-                    setFabEnabled(true);
-                }
+        public void loadStateFrom(Bundle savedInstanceState) {
+            mActionModeClosedByDrawer = savedInstanceState.getBoolean(
+                KEY_ACTION_MODE_CLOSED_BY_DRAWER,
+                mActionModeClosedByDrawer
+            );
+            SparseBooleanArrayParcelable sbap = savedInstanceState.getParcelable(
+                KEY_SELECTION_WHEN_CLOSED_BY_DRAWER
+            );
+            if (sbap != null) {
+                mSelectionWhenActionModeClosedByDrawer = sbap.getSparseBooleanArray();
             }
-        });
+        }
+    }
+
+    /**
+     * Init listener that will handle interactions in multiple selection mode.
+     */
+    private void setChoiceModeAsMultipleModal(Bundle savedInstanceState) {
+        setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+        mMultiChoiceModeListener = new MultiChoiceModeListener();
+        if (savedInstanceState != null) {
+            mMultiChoiceModeListener.loadStateFrom(savedInstanceState);
+        }
+        setMultiChoiceModeListener(mMultiChoiceModeListener);
+        ((FileActivity)getActivity()).addDrawerListener(mMultiChoiceModeListener);
     }
 
     /**
@@ -425,6 +549,7 @@ public class OCFileListFragment extends ExtendedListFragment {
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putParcelable(KEY_FILE, mFile);
+        mMultiChoiceModeListener.storeStateIn(outState);
     }
 
     @Override

+ 91 - 0
src/com/owncloud/android/ui/helpers/SparseBooleanArrayParcelable.java

@@ -0,0 +1,91 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author David A. Velasco
+ *   Copyright (C) 2016 ownCloud GmbH.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.ui.helpers;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseBooleanArray;
+
+/**
+ * Wraps a SparseBooleanArrayParcelable to allow its serialization and desearialization
+ * through {@link Parcelable} interface.
+ */
+public class SparseBooleanArrayParcelable implements Parcelable {
+
+    public static Parcelable.Creator<SparseBooleanArrayParcelable> CREATOR =
+        new Parcelable.Creator<SparseBooleanArrayParcelable>() {
+
+        @Override
+        public SparseBooleanArrayParcelable createFromParcel(Parcel source) {
+            // read size of array from source
+            int size = source.readInt();
+
+            // then pairs of (key, value)s, in the object to wrap
+            SparseBooleanArray sba = new SparseBooleanArray();
+            int key;
+            boolean value;
+            for (int i = 0; i < size; i++) {
+                key = source.readInt();
+                value = (source.readInt() != 0);
+                sba.put(key, value);
+            }
+
+            // wrap SparseBooleanArray
+            return new SparseBooleanArrayParcelable(sba);
+        }
+
+        @Override
+        public SparseBooleanArrayParcelable[] newArray(int size) {
+            return new SparseBooleanArrayParcelable[size];
+        }
+    };
+
+    private final SparseBooleanArray mSba;
+
+    public SparseBooleanArrayParcelable(SparseBooleanArray sba) {
+        if (sba == null) {
+            throw new IllegalArgumentException("Cannot wrap a null SparseBooleanArray");
+        }
+        mSba = sba;
+    }
+
+    public SparseBooleanArray getSparseBooleanArray() {
+        return mSba;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // first, size of the array
+        dest.writeInt(mSba.size());
+
+        // then, pairs of (key, value)
+        for (int i = 0; i < mSba.size(); i++) {
+            dest.writeInt(mSba.keyAt(i));
+            dest.writeInt(mSba.valueAt(i) ? 1 : 0);
+        }
+
+    }
+}