mirror of
https://github.com/nextcloud/android.git
synced 2024-11-26 15:15:51 +03:00
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:
parent
175816b70e
commit
5a97d31392
4 changed files with 297 additions and 66 deletions
|
@ -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}"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue