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
This commit is contained in:
David A. Velasco 2016-10-10 11:16:24 +02:00 committed by AndyScherzinger
parent 175816b70e
commit 5a97d31392
No known key found for this signature in database
GPG key ID: 6CADC7E3523C308B
4 changed files with 297 additions and 66 deletions

View file

@ -41,7 +41,6 @@ dependencies {
compile "com.android.support:design:${supportLibraryVersion}" compile "com.android.support:design:${supportLibraryVersion}"
compile 'com.jakewharton:disklrucache:2.0.2' compile 'com.jakewharton:disklrucache:2.0.2'
compile "com.android.support:appcompat-v7:${supportLibraryVersion}" compile "com.android.support:appcompat-v7:${supportLibraryVersion}"
compile "com.android.support:cardview-v7:${supportLibraryVersion}"
compile 'com.getbase:floatingactionbutton:1.10.1' compile 'com.getbase:floatingactionbutton:1.10.1'
/// dependencies for local unit tests /// dependencies for local unit tests
@ -62,7 +61,10 @@ dependencies {
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' 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 // 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}"
} }

View file

@ -198,7 +198,7 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
}; };
// Set the drawer toggle as the DrawerListener // Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle); mDrawerLayout.addDrawerListener(mDrawerToggle);
mDrawerToggle.setDrawerIndicatorEnabled(true); mDrawerToggle.setDrawerIndicatorEnabled(true);
} }
@ -796,4 +796,17 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
} }
return false; 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");
}
}
} }

View file

@ -28,7 +28,9 @@ import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import android.util.SparseBooleanArray;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; 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.CreateFolderDialogFragment;
import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
import com.owncloud.android.ui.dialog.RenameFileDialogFragment; 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.PreviewImageFragment;
import com.owncloud.android.ui.preview.PreviewMediaFragment; import com.owncloud.android.ui.preview.PreviewMediaFragment;
import com.owncloud.android.ui.preview.PreviewTextFragment; import com.owncloud.android.ui.preview.PreviewTextFragment;
@ -107,6 +111,7 @@ public class OCFileListFragment extends ExtendedListFragment {
private boolean mHideFab = true; private boolean mHideFab = true;
private boolean miniFabClicked = false; private boolean miniFabClicked = false;
private ActionMode mActiveActionMode; private ActionMode mActiveActionMode;
private OCFileListFragment.MultiChoiceModeListener mMultiChoiceModeListener;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -151,7 +156,7 @@ public class OCFileListFragment extends ExtendedListFragment {
Bundle args = getArguments(); Bundle args = getArguments();
boolean allowContextualActions = (args != null) && args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, false); boolean allowContextualActions = (args != null) && args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, false);
if (allowContextualActions) { if (allowContextualActions) {
setChoiceModeAsMultipleModal(); setChoiceModeAsMultipleModal(savedInstanceState);
} }
Log_OC.i(TAG, "onCreateView() end"); Log_OC.i(TAG, "onCreateView() end");
return v; return v;
@ -346,76 +351,195 @@ public class OCFileListFragment extends ExtendedListFragment {
com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE); 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) { * Selected items in list when action mode is closed by drawer
getListView().invalidateViews(); */
mode.invalidate(); private SparseBooleanArray mSelectionWhenActionModeClosedByDrawer = null;
}
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public void onDrawerSlide(View drawerView, float slideOffset) {
mActiveActionMode = mode; // nothing to do
}
MenuInflater inflater = getActivity().getMenuInflater(); @Override
inflater.inflate(R.menu.file_actions_menu, menu); public void onDrawerOpened(View drawerView) {
mode.invalidate(); // nothing to do
}
//set gray color /**
DisplayUtils.colorStatusBar(getActivity(), mSystemBarActionModeColor); * When the navigation drawer is closed, action mode is recovered in the same state as was
DisplayUtils.colorToolbarProgressBar(getActivity(), mProgressBarActionModeColor); * when the drawer was (started to be) opened.
*
// hide FAB in multi selection mode * @param drawerView Navigation drawer just closed.
setFabEnabled(false); */
@Override
return true; public void onDrawerClosed(View drawerView) {
} if (mSelectionWhenActionModeClosedByDrawer !=null && mActionModeClosedByDrawer) {
for (int i = 0; i< mSelectionWhenActionModeClosedByDrawer.size(); i++) {
@Override if (mSelectionWhenActionModeClosedByDrawer.valueAt(i)) {
public boolean onPrepareActionMode(ActionMode mode, Menu menu) { getListView().setItemChecked(
List<OCFile> checkedFiles = mAdapter.getCheckedItems(getListView()); mSelectionWhenActionModeClosedByDrawer.keyAt(i),
final int checkedCount = checkedFiles.size(); true
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;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return onFileActionChosen(item.getItemId());
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActiveActionMode = null;
// reset to previous color
DisplayUtils.colorStatusBar(getActivity(), mSystemBarColor);
DisplayUtils.colorToolbarProgressBar(getActivity(), mProgressBarColor);
// show FAB on multi selection mode exit
if(!mHideFab) {
setFabEnabled(true);
} }
} }
}); mSelectionWhenActionModeClosedByDrawer = null;
}
/**
* 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;
}
}
/**
* 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();
}
/**
* 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);
}
// hide FAB in multi selection mode
setFabEnabled(false);
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);
}
// show FAB on multi selection mode exit
if(!mHideFab) {
setFabEnabled(true);
}
}
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);
}
}
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) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putParcelable(KEY_FILE, mFile); outState.putParcelable(KEY_FILE, mFile);
mMultiChoiceModeListener.storeStateIn(outState);
} }
@Override @Override

View file

@ -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);
}
}
}