OCFileListFragment.java 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author Bartek Przybylski
  5. * @author masensio
  6. * @author David A. Velasco
  7. * Copyright (C) 2011 Bartek Przybylski
  8. * Copyright (C) 2016 ownCloud Inc.
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License version 2,
  12. * as published by the Free Software Foundation.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. package com.owncloud.android.ui.fragment;
  24. import android.content.Context;
  25. import android.content.Intent;
  26. import android.content.SharedPreferences;
  27. import android.os.Build;
  28. import android.os.Bundle;
  29. import android.preference.PreferenceManager;
  30. import android.support.v4.widget.DrawerLayout;
  31. import android.support.v4.widget.SwipeRefreshLayout;
  32. import android.util.SparseBooleanArray;
  33. import android.view.ActionMode;
  34. import android.view.LayoutInflater;
  35. import android.view.Menu;
  36. import android.view.MenuInflater;
  37. import android.view.MenuItem;
  38. import android.view.View;
  39. import android.view.ViewGroup;
  40. import android.widget.AbsListView;
  41. import android.widget.AdapterView;
  42. import android.widget.ListView;
  43. import android.widget.TextView;
  44. import android.widget.Toast;
  45. import com.owncloud.android.MainApp;
  46. import com.owncloud.android.R;
  47. import com.owncloud.android.authentication.AccountUtils;
  48. import com.owncloud.android.datamodel.FileDataStorageManager;
  49. import com.owncloud.android.datamodel.OCFile;
  50. import com.owncloud.android.files.FileMenuFilter;
  51. import com.owncloud.android.lib.common.utils.Log_OC;
  52. import com.owncloud.android.lib.resources.status.OwnCloudVersion;
  53. import com.owncloud.android.ui.activity.FileActivity;
  54. import com.owncloud.android.ui.activity.FileDisplayActivity;
  55. import com.owncloud.android.ui.activity.FolderPickerActivity;
  56. import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
  57. import com.owncloud.android.ui.activity.UploadFilesActivity;
  58. import com.owncloud.android.ui.adapter.FileListListAdapter;
  59. import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
  60. import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
  61. import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
  62. import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
  63. import com.owncloud.android.ui.helpers.SparseBooleanArrayParcelable;
  64. import com.owncloud.android.ui.preview.PreviewImageFragment;
  65. import com.owncloud.android.ui.preview.PreviewMediaFragment;
  66. import com.owncloud.android.ui.preview.PreviewTextFragment;
  67. import com.owncloud.android.utils.DisplayUtils;
  68. import com.owncloud.android.utils.FileStorageUtils;
  69. import java.io.File;
  70. import java.util.ArrayList;
  71. import java.util.List;
  72. /**
  73. * A Fragment that lists all files and folders in a given path.
  74. *
  75. * TODO refactor to get rid of direct dependency on FileDisplayActivity
  76. */
  77. public class OCFileListFragment extends ExtendedListFragment {
  78. private static final String TAG = OCFileListFragment.class.getSimpleName();
  79. private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ?
  80. OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
  81. public final static String ARG_JUST_FOLDERS = MY_PACKAGE + ".JUST_FOLDERS";
  82. public final static String ARG_ALLOW_CONTEXTUAL_ACTIONS = MY_PACKAGE + ".ALLOW_CONTEXTUAL";
  83. public final static String ARG_HIDE_FAB = MY_PACKAGE + ".HIDE_FAB";
  84. private static final String KEY_FILE = MY_PACKAGE + ".extra.FILE";
  85. private static final String KEY_FAB_EVER_CLICKED = "FAB_EVER_CLICKED";
  86. private static final String GRID_IS_PREFERED_PREFERENCE = "gridIsPrefered";
  87. private static String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER";
  88. private FileFragment.ContainerActivity mContainerActivity;
  89. private OCFile mFile = null;
  90. private FileListListAdapter mAdapter;
  91. private boolean mJustFolders;
  92. private int mSystemBarActionModeColor;
  93. private int mSystemBarColor;
  94. private int mProgressBarActionModeColor;
  95. private int mProgressBarColor;
  96. private boolean mHideFab = true;
  97. private boolean miniFabClicked = false;
  98. private ActionMode mActiveActionMode;
  99. private OCFileListFragment.MultiChoiceModeListener mMultiChoiceModeListener;
  100. @Override
  101. public void onCreate(Bundle savedInstanceState) {
  102. super.onCreate(savedInstanceState);
  103. setHasOptionsMenu(true);
  104. mSystemBarActionModeColor = getResources().getColor(R.color.action_mode_status_bar_background);
  105. mSystemBarColor = getResources().getColor(R.color.primary_dark);
  106. mProgressBarActionModeColor = getResources().getColor(R.color.action_mode_background);
  107. mProgressBarColor = getResources().getColor(R.color.primary);
  108. }
  109. /**
  110. * {@inheritDoc}
  111. */
  112. @Override
  113. public void onAttach(Context context) {
  114. super.onAttach(context);
  115. Log_OC.i(TAG, "onAttach");
  116. try {
  117. mContainerActivity = (FileFragment.ContainerActivity) context;
  118. } catch (ClassCastException e) {
  119. throw new ClassCastException(context.toString() + " must implement " +
  120. FileFragment.ContainerActivity.class.getSimpleName());
  121. }
  122. try {
  123. setOnRefreshListener((OnEnforceableRefreshListener) context);
  124. } catch (ClassCastException e) {
  125. throw new ClassCastException(context.toString() + " must implement " +
  126. SwipeRefreshLayout.OnRefreshListener.class.getSimpleName());
  127. }
  128. }
  129. /**
  130. * {@inheritDoc}
  131. */
  132. @Override
  133. public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  134. Log_OC.i(TAG, "onCreateView() start");
  135. View v = super.onCreateView(inflater, container, savedInstanceState);
  136. Bundle args = getArguments();
  137. boolean allowContextualActions = (args != null) && args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, false);
  138. if (allowContextualActions) {
  139. setChoiceModeAsMultipleModal(savedInstanceState);
  140. }
  141. Log_OC.i(TAG, "onCreateView() end");
  142. return v;
  143. }
  144. @Override
  145. public void onDetach() {
  146. setOnRefreshListener(null);
  147. mContainerActivity = null;
  148. super.onDetach();
  149. }
  150. /**
  151. * {@inheritDoc}
  152. */
  153. @Override
  154. public void onActivityCreated(Bundle savedInstanceState) {
  155. super.onActivityCreated(savedInstanceState);
  156. Log_OC.i(TAG, "onActivityCreated() start");
  157. if (savedInstanceState != null) {
  158. mFile = savedInstanceState.getParcelable(KEY_FILE);
  159. }
  160. if (mJustFolders) {
  161. setFooterEnabled(false);
  162. } else {
  163. setFooterEnabled(true);
  164. }
  165. Bundle args = getArguments();
  166. mJustFolders = (args != null) && args.getBoolean(ARG_JUST_FOLDERS, false);
  167. mAdapter = new FileListListAdapter(
  168. mJustFolders,
  169. getActivity(),
  170. mContainerActivity
  171. );
  172. setListAdapter(mAdapter);
  173. mHideFab = (args != null) && args.getBoolean(ARG_HIDE_FAB, false);
  174. if (mHideFab) {
  175. setFabEnabled(false);
  176. } else {
  177. setFabEnabled(true);
  178. registerFabListeners();
  179. // detect if a mini FAB has ever been clicked
  180. final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
  181. if(prefs.getLong(KEY_FAB_EVER_CLICKED, 0) > 0) {
  182. miniFabClicked = true;
  183. }
  184. // add labels to the min FABs when none of them has ever been clicked on
  185. if(!miniFabClicked) {
  186. setFabLabels();
  187. } else {
  188. removeFabLabels();
  189. }
  190. }
  191. }
  192. /**
  193. * adds labels to all mini FABs.
  194. */
  195. private void setFabLabels() {
  196. getFabUpload().setTitle(getResources().getString(R.string.actionbar_upload));
  197. getFabMkdir().setTitle(getResources().getString(R.string.actionbar_mkdir));
  198. getFabUploadFromApp().setTitle(getResources().getString(R.string.actionbar_upload_from_apps));
  199. }
  200. /**
  201. * registers all listeners on all mini FABs.
  202. */
  203. private void registerFabListeners() {
  204. registerFabUploadListeners();
  205. registerFabMkDirListeners();
  206. registerFabUploadFromAppListeners();
  207. }
  208. /**
  209. * registers {@link android.view.View.OnClickListener} and {@link android.view.View.OnLongClickListener}
  210. * on the Upload mini FAB for the linked action and {@link Toast} showing the underlying action.
  211. */
  212. private void registerFabUploadListeners() {
  213. getFabUpload().setOnClickListener(new View.OnClickListener() {
  214. @Override
  215. public void onClick(View v) {
  216. UploadFilesActivity.startUploadActivityForResult(getActivity(), ((FileActivity) getActivity())
  217. .getAccount(), FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM);
  218. getFabMain().collapse();
  219. recordMiniFabClick();
  220. }
  221. });
  222. getFabUpload().setOnLongClickListener(new View.OnLongClickListener() {
  223. @Override
  224. public boolean onLongClick(View v) {
  225. Toast.makeText(getActivity(), R.string.actionbar_upload, Toast.LENGTH_SHORT).show();
  226. return true;
  227. }
  228. });
  229. }
  230. /**
  231. * registers {@link android.view.View.OnClickListener} and {@link android.view.View.OnLongClickListener}
  232. * on the 'Create Dir' mini FAB for the linked action and {@link Toast} showing the underlying action.
  233. */
  234. private void registerFabMkDirListeners() {
  235. getFabMkdir().setOnClickListener(new View.OnClickListener() {
  236. @Override
  237. public void onClick(View v) {
  238. CreateFolderDialogFragment dialog =
  239. CreateFolderDialogFragment.newInstance(mFile);
  240. dialog.show(getActivity().getSupportFragmentManager(), DIALOG_CREATE_FOLDER);
  241. getFabMain().collapse();
  242. recordMiniFabClick();
  243. }
  244. });
  245. getFabMkdir().setOnLongClickListener(new View.OnLongClickListener() {
  246. @Override
  247. public boolean onLongClick(View v) {
  248. Toast.makeText(getActivity(), R.string.actionbar_mkdir, Toast.LENGTH_SHORT).show();
  249. return true;
  250. }
  251. });
  252. }
  253. /**
  254. * registers {@link android.view.View.OnClickListener} and {@link android.view.View.OnLongClickListener}
  255. * on the Upload from App mini FAB for the linked action and {@link Toast} showing the underlying action.
  256. */
  257. private void registerFabUploadFromAppListeners() {
  258. getFabUploadFromApp().setOnClickListener(new View.OnClickListener() {
  259. @Override
  260. public void onClick(View v) {
  261. Intent action = new Intent(Intent.ACTION_GET_CONTENT);
  262. action = action.setType("*/*").addCategory(Intent.CATEGORY_OPENABLE);
  263. //Intent.EXTRA_ALLOW_MULTIPLE is only supported on api level 18+, Jelly Bean
  264. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
  265. action.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
  266. }
  267. getActivity().startActivityForResult(
  268. Intent.createChooser(action, getString(R.string.upload_chooser_title)),
  269. FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS
  270. );
  271. getFabMain().collapse();
  272. recordMiniFabClick();
  273. }
  274. });
  275. getFabUploadFromApp().setOnLongClickListener(new View.OnLongClickListener() {
  276. @Override
  277. public boolean onLongClick(View v) {
  278. Toast.makeText(getActivity(),
  279. R.string.actionbar_upload_from_apps,
  280. Toast.LENGTH_SHORT).show();
  281. return true;
  282. }
  283. });
  284. }
  285. /**
  286. * records a click on a mini FAB and thus:
  287. * <ol>
  288. * <li>persists the click fact</li>
  289. * <li>removes the mini FAB labels</li>
  290. * </ol>
  291. */
  292. private void recordMiniFabClick() {
  293. // only record if it hasn't been done already at some other time
  294. if(!miniFabClicked) {
  295. final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
  296. sp.edit().putLong(KEY_FAB_EVER_CLICKED, 1).apply();
  297. miniFabClicked = true;
  298. }
  299. }
  300. /**
  301. * removes the labels on all known min FABs.
  302. */
  303. private void removeFabLabels() {
  304. getFabUpload().setTitle(null);
  305. getFabMkdir().setTitle(null);
  306. getFabUploadFromApp().setTitle(null);
  307. ((TextView) getFabUpload().getTag(
  308. com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE);
  309. ((TextView) getFabMkdir().getTag(
  310. com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE);
  311. ((TextView) getFabUploadFromApp().getTag(
  312. com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE);
  313. }
  314. /**
  315. * Handler for multiple selection mode.
  316. *
  317. * Manages input from the user when one or more files or folders are selected in the list.
  318. *
  319. * Also listens to changes in navigation drawer to hide and recover multiple selection when it's opened
  320. * and closed.
  321. */
  322. private class MultiChoiceModeListener
  323. implements AbsListView.MultiChoiceModeListener, DrawerLayout.DrawerListener {
  324. private static final String KEY_ACTION_MODE_CLOSED_BY_DRAWER = "KILLED_ACTION_MODE";
  325. private static final String KEY_SELECTION_WHEN_CLOSED_BY_DRAWER = "CHECKED_ITEMS";
  326. /**
  327. * True when action mode is finished because the drawer was opened
  328. */
  329. private boolean mActionModeClosedByDrawer = false;
  330. /**
  331. * Selected items in list when action mode is closed by drawer
  332. */
  333. private SparseBooleanArray mSelectionWhenActionModeClosedByDrawer = null;
  334. @Override
  335. public void onDrawerSlide(View drawerView, float slideOffset) {
  336. // nothing to do
  337. }
  338. @Override
  339. public void onDrawerOpened(View drawerView) {
  340. // nothing to do
  341. }
  342. /**
  343. * When the navigation drawer is closed, action mode is recovered in the same state as was
  344. * when the drawer was (started to be) opened.
  345. *
  346. * @param drawerView Navigation drawer just closed.
  347. */
  348. @Override
  349. public void onDrawerClosed(View drawerView) {
  350. if (mSelectionWhenActionModeClosedByDrawer !=null && mActionModeClosedByDrawer) {
  351. for (int i = 0; i< mSelectionWhenActionModeClosedByDrawer.size(); i++) {
  352. if (mSelectionWhenActionModeClosedByDrawer.valueAt(i)) {
  353. getListView().setItemChecked(
  354. mSelectionWhenActionModeClosedByDrawer.keyAt(i),
  355. true
  356. );
  357. }
  358. }
  359. }
  360. mSelectionWhenActionModeClosedByDrawer = null;
  361. }
  362. /**
  363. * If the action mode is active when the navigation drawer starts to move, the action
  364. * mode is closed and the selection stored to be recovered when the drawer is closed.
  365. *
  366. * @param newState One of STATE_IDLE, STATE_DRAGGING or STATE_SETTLING.
  367. */
  368. @Override
  369. public void onDrawerStateChanged(int newState) {
  370. if (DrawerLayout.STATE_DRAGGING == newState && mActiveActionMode != null) {
  371. mSelectionWhenActionModeClosedByDrawer = getListView().getCheckedItemPositions().clone();
  372. mActiveActionMode.finish();
  373. mActionModeClosedByDrawer = true;
  374. }
  375. }
  376. /**
  377. * Update action mode bar when an item is selected / unselected in the list
  378. */
  379. @Override
  380. public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
  381. getListView().invalidateViews();
  382. mode.invalidate();
  383. }
  384. /**
  385. * Load menu and customize UI when action mode is started.
  386. */
  387. @Override
  388. public boolean onCreateActionMode(ActionMode mode, Menu menu) {
  389. mActiveActionMode = mode;
  390. MenuInflater inflater = getActivity().getMenuInflater();
  391. inflater.inflate(R.menu.file_actions_menu, menu);
  392. mode.invalidate();
  393. //set gray color
  394. DisplayUtils.colorStatusBar(getActivity(), mSystemBarActionModeColor);
  395. DisplayUtils.colorToolbarProgressBar(getActivity(), mProgressBarActionModeColor);
  396. // hide FAB in multi selection mode
  397. setFabEnabled(false);
  398. return true;
  399. }
  400. /**
  401. * Updates available action in menu depending on current selection.
  402. */
  403. @Override
  404. public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
  405. List<OCFile> checkedFiles = mAdapter.getCheckedItems(getListView());
  406. final int checkedCount = checkedFiles.size();
  407. String title = getResources().getQuantityString(
  408. R.plurals.items_selected_count,
  409. checkedCount,
  410. checkedCount
  411. );
  412. mode.setTitle(title);
  413. FileMenuFilter mf = new FileMenuFilter(
  414. checkedFiles,
  415. ((FileActivity) getActivity()).getAccount(),
  416. mContainerActivity,
  417. getActivity()
  418. );
  419. mf.filter(menu);
  420. return true;
  421. }
  422. /**
  423. * Starts the corresponding action when a menu item is tapped by the user.
  424. */
  425. @Override
  426. public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
  427. return onFileActionChosen(item.getItemId());
  428. }
  429. /**
  430. * Restores UI.
  431. */
  432. @Override
  433. public void onDestroyActionMode(ActionMode mode) {
  434. mActiveActionMode = null;
  435. // reset to previous color
  436. DisplayUtils.colorStatusBar(getActivity(), mSystemBarColor);
  437. DisplayUtils.colorToolbarProgressBar(getActivity(), mProgressBarColor);
  438. // show FAB on multi selection mode exit
  439. if(!mHideFab) {
  440. setFabEnabled(true);
  441. }
  442. }
  443. public void storeStateIn(Bundle outState) {
  444. outState.putBoolean(KEY_ACTION_MODE_CLOSED_BY_DRAWER, mActionModeClosedByDrawer);
  445. if (mSelectionWhenActionModeClosedByDrawer != null) {
  446. SparseBooleanArrayParcelable sbap = new SparseBooleanArrayParcelable(
  447. mSelectionWhenActionModeClosedByDrawer
  448. );
  449. outState.putParcelable(KEY_SELECTION_WHEN_CLOSED_BY_DRAWER, sbap);
  450. }
  451. }
  452. public void loadStateFrom(Bundle savedInstanceState) {
  453. mActionModeClosedByDrawer = savedInstanceState.getBoolean(
  454. KEY_ACTION_MODE_CLOSED_BY_DRAWER,
  455. mActionModeClosedByDrawer
  456. );
  457. SparseBooleanArrayParcelable sbap = savedInstanceState.getParcelable(
  458. KEY_SELECTION_WHEN_CLOSED_BY_DRAWER
  459. );
  460. if (sbap != null) {
  461. mSelectionWhenActionModeClosedByDrawer = sbap.getSparseBooleanArray();
  462. }
  463. }
  464. }
  465. /**
  466. * Init listener that will handle interactions in multiple selection mode.
  467. */
  468. private void setChoiceModeAsMultipleModal(Bundle savedInstanceState) {
  469. setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
  470. mMultiChoiceModeListener = new MultiChoiceModeListener();
  471. if (savedInstanceState != null) {
  472. mMultiChoiceModeListener.loadStateFrom(savedInstanceState);
  473. }
  474. setMultiChoiceModeListener(mMultiChoiceModeListener);
  475. ((FileActivity)getActivity()).addDrawerListener(mMultiChoiceModeListener);
  476. }
  477. /**
  478. * Saves the current listed folder.
  479. */
  480. @Override
  481. public void onSaveInstanceState(Bundle outState) {
  482. super.onSaveInstanceState(outState);
  483. outState.putParcelable(KEY_FILE, mFile);
  484. mMultiChoiceModeListener.storeStateIn(outState);
  485. }
  486. @Override
  487. public void onPrepareOptionsMenu (Menu menu) {
  488. changeGridIcon(menu); // this is enough if the option stays out of the action bar
  489. }
  490. /**
  491. * Call this, when the user presses the up button.
  492. *
  493. * Tries to move up the current folder one level. If the parent folder was removed from the
  494. * database, it continues browsing up until finding an existing folders.
  495. * <p/>
  496. * return Count of folder levels browsed up.
  497. */
  498. public int onBrowseUp() {
  499. OCFile parentDir;
  500. int moveCount = 0;
  501. if (mFile != null) {
  502. FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
  503. String parentPath = null;
  504. if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
  505. parentPath = new File(mFile.getRemotePath()).getParent();
  506. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
  507. parentPath + OCFile.PATH_SEPARATOR;
  508. parentDir = storageManager.getFileByPath(parentPath);
  509. moveCount++;
  510. } else {
  511. parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);
  512. }
  513. while (parentDir == null) {
  514. parentPath = new File(parentPath).getParent();
  515. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
  516. parentPath + OCFile.PATH_SEPARATOR;
  517. parentDir = storageManager.getFileByPath(parentPath);
  518. moveCount++;
  519. } // exit is granted because storageManager.getFileByPath("/") never returns null
  520. mFile = parentDir;
  521. listDirectory(mFile, MainApp.getOnlyOnDevice());
  522. onRefresh(false);
  523. // restore index and top position
  524. restoreIndexAndTopPosition();
  525. } // else - should never happen now
  526. return moveCount;
  527. }
  528. @Override
  529. public void onItemClick(AdapterView<?> l, View v, int position, long id) {
  530. OCFile file = (OCFile) mAdapter.getItem(position);
  531. if (file != null) {
  532. if (file.isFolder()) {
  533. // update state and view of this fragment
  534. listDirectory(file, MainApp.getOnlyOnDevice());
  535. // then, notify parent activity to let it update its state and view
  536. mContainerActivity.onBrowsedDownTo(file);
  537. // save index and top position
  538. saveIndexAndTopPosition(position);
  539. } else { /// Click on a file
  540. if (PreviewImageFragment.canBePreviewed(file)) {
  541. // preview image - it handles the download, if needed
  542. ((FileDisplayActivity)mContainerActivity).startImagePreview(file);
  543. } else if (PreviewTextFragment.canBePreviewed(file)){
  544. ((FileDisplayActivity)mContainerActivity).startTextPreview(file);
  545. } else if (file.isDown()) {
  546. if (PreviewMediaFragment.canBePreviewed(file)) {
  547. // media preview
  548. ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true);
  549. } else {
  550. mContainerActivity.getFileOperationsHelper().openFile(file);
  551. }
  552. } else {
  553. // automatic download, preview on finish
  554. ((FileDisplayActivity) mContainerActivity).startDownloadForPreview(file);
  555. }
  556. }
  557. } else {
  558. Log_OC.d(TAG, "Null object in ListAdapter!!");
  559. }
  560. }
  561. /**
  562. * Start the appropriate action(s) on the currently selected files given menu selected by the user.
  563. *
  564. * @param menuId Identifier of the action menu selected by the user
  565. * @return 'true' if the menu selection started any action, 'false' otherwise.
  566. */
  567. public boolean onFileActionChosen(int menuId) {
  568. final ArrayList<OCFile> checkedFiles = mAdapter.getCheckedItems(getListView());
  569. if (checkedFiles.size() <= 0) return false;
  570. if (checkedFiles.size() == 1) {
  571. /// action only possible on a single file
  572. OCFile singleFile = checkedFiles.get(0);
  573. switch (menuId) {
  574. case R.id.action_share_file: {
  575. mContainerActivity.getFileOperationsHelper().showShareFile(singleFile);
  576. return true;
  577. }
  578. case R.id.action_open_file_with: {
  579. mContainerActivity.getFileOperationsHelper().openFile(singleFile);
  580. return true;
  581. }
  582. case R.id.action_rename_file: {
  583. RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(singleFile);
  584. dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
  585. return true;
  586. }
  587. case R.id.action_see_details: {
  588. if (mActiveActionMode != null) {
  589. mActiveActionMode.finish();
  590. }
  591. mContainerActivity.showDetails(singleFile);
  592. return true;
  593. }
  594. case R.id.action_send_file: {
  595. // Obtain the file
  596. if (!singleFile.isDown()) { // Download the file
  597. Log_OC.d(TAG, singleFile.getRemotePath() + " : File must be downloaded");
  598. ((FileDisplayActivity) mContainerActivity).startDownloadForSending(singleFile);
  599. } else {
  600. mContainerActivity.getFileOperationsHelper().sendDownloadedFile(singleFile);
  601. }
  602. return true;
  603. }
  604. }
  605. }
  606. /// actions possible on a batch of files
  607. switch (menuId) {
  608. case R.id.action_remove_file: {
  609. RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(checkedFiles);
  610. dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
  611. return true;
  612. }
  613. case R.id.action_download_file:
  614. case R.id.action_sync_file: {
  615. mContainerActivity.getFileOperationsHelper().syncFiles(checkedFiles);
  616. return true;
  617. }
  618. case R.id.action_cancel_sync: {
  619. ((FileDisplayActivity) mContainerActivity).cancelTransference(checkedFiles);
  620. return true;
  621. }
  622. case R.id.action_favorite_file: {
  623. mContainerActivity.getFileOperationsHelper().toggleFavorites(checkedFiles, true);
  624. return true;
  625. }
  626. case R.id.action_unfavorite_file: {
  627. mContainerActivity.getFileOperationsHelper().toggleFavorites(checkedFiles, false);
  628. return true;
  629. }
  630. case R.id.action_move: {
  631. Intent action = new Intent(getActivity(), FolderPickerActivity.class);
  632. action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, checkedFiles);
  633. action.putExtra(FolderPickerActivity.EXTRA_ACTION, getResources().getText(R.string.move_to));
  634. getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES);
  635. return true;
  636. }
  637. case R.id.action_copy:
  638. Intent action = new Intent(getActivity(), FolderPickerActivity.class);
  639. action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, checkedFiles);
  640. action.putExtra(FolderPickerActivity.EXTRA_ACTION, getResources().getText(R.string.copy_to));
  641. getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES);
  642. return true;
  643. default:
  644. return false;
  645. }
  646. }
  647. /**
  648. * Use this to query the {@link OCFile} that is currently
  649. * being displayed by this fragment
  650. *
  651. * @return The currently viewed OCFile
  652. */
  653. public OCFile getCurrentFile() {
  654. return mFile;
  655. }
  656. /**
  657. * Calls {@link OCFileListFragment#listDirectory(OCFile, boolean)} with a null parameter
  658. */
  659. public void listDirectory(boolean onlyOnDevice){
  660. listDirectory(null, onlyOnDevice);
  661. }
  662. public void refreshDirectory(){
  663. listDirectory(getCurrentFile(), MainApp.getOnlyOnDevice());
  664. }
  665. /**
  666. * Lists the given directory on the view. When the input parameter is null,
  667. * it will either refresh the last known directory. list the root
  668. * if there never was a directory.
  669. *
  670. * @param directory File to be listed
  671. */
  672. public void listDirectory(OCFile directory, boolean onlyOnDevice) {
  673. FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
  674. if (storageManager != null) {
  675. // Check input parameters for null
  676. if (directory == null) {
  677. if (mFile != null) {
  678. directory = mFile;
  679. } else {
  680. directory = storageManager.getFileByPath("/");
  681. if (directory == null) return; // no files, wait for sync
  682. }
  683. }
  684. // If that's not a directory -> List its parent
  685. if (!directory.isFolder()) {
  686. Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
  687. directory = storageManager.getFileById(directory.getParentId());
  688. }
  689. mAdapter.swapDirectory(directory, storageManager, onlyOnDevice);
  690. if (mFile == null || !mFile.equals(directory)) {
  691. mCurrentListView.setSelection(0);
  692. }
  693. mFile = directory;
  694. updateLayout();
  695. }
  696. }
  697. private void updateLayout() {
  698. if (!mJustFolders) {
  699. int filesCount = 0, foldersCount = 0;
  700. int count = mAdapter.getCount();
  701. OCFile file;
  702. for (int i=0; i < count ; i++) {
  703. file = (OCFile) mAdapter.getItem(i);
  704. if (file.isFolder()) {
  705. foldersCount++;
  706. } else {
  707. if (!file.isHidden()) {
  708. filesCount++;
  709. }
  710. }
  711. }
  712. // set footer text
  713. setFooterText(generateFooterText(filesCount, foldersCount));
  714. // decide grid vs list view
  715. OwnCloudVersion version = AccountUtils.getServerVersion(
  716. ((FileActivity)mContainerActivity).getAccount());
  717. if (version != null && version.supportsRemoteThumbnails() &&
  718. isGridViewPreferred(mFile)) {
  719. switchToGridView();
  720. } else {
  721. switchToListView();
  722. }
  723. }
  724. invalidateActionMode();
  725. }
  726. private void invalidateActionMode() {
  727. if(mActiveActionMode != null){
  728. mActiveActionMode.invalidate();
  729. }
  730. }
  731. private String generateFooterText(int filesCount, int foldersCount) {
  732. String output;
  733. if (filesCount <= 0) {
  734. if (foldersCount <= 0) {
  735. output = "";
  736. } else if (foldersCount == 1) {
  737. output = getResources().getString(R.string.file_list__footer__folder);
  738. } else { // foldersCount > 1
  739. output = getResources().getString(R.string.file_list__footer__folders, foldersCount);
  740. }
  741. } else if (filesCount == 1) {
  742. if (foldersCount <= 0) {
  743. output = getResources().getString(R.string.file_list__footer__file);
  744. } else if (foldersCount == 1) {
  745. output = getResources().getString(R.string.file_list__footer__file_and_folder);
  746. } else { // foldersCount > 1
  747. output = getResources().getString(R.string.file_list__footer__file_and_folders, foldersCount);
  748. }
  749. } else { // filesCount > 1
  750. if (foldersCount <= 0) {
  751. output = getResources().getString(R.string.file_list__footer__files, filesCount);
  752. } else if (foldersCount == 1) {
  753. output = getResources().getString(R.string.file_list__footer__files_and_folder, filesCount);
  754. } else { // foldersCount > 1
  755. output = getResources().getString(
  756. R.string.file_list__footer__files_and_folders, filesCount, foldersCount
  757. );
  758. }
  759. }
  760. return output;
  761. }
  762. public void sortByName(boolean descending) {
  763. mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
  764. }
  765. public void sortByDate(boolean descending) {
  766. mAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending);
  767. }
  768. public void sortBySize(boolean descending) {
  769. mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
  770. }
  771. /**
  772. * Determines if user set folder to grid or list view. If folder is not set itself,
  773. * it finds a parent that is set (at least root is set).
  774. * @param file Folder to check.
  775. * @return 'true' is folder should be shown in grid mode, 'false' if list mode is preferred.
  776. */
  777. public boolean isGridViewPreferred(OCFile file){
  778. if (file != null) {
  779. OCFile fileToTest = file;
  780. OCFile parentDir;
  781. String parentPath = null;
  782. FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
  783. SharedPreferences setting =
  784. getActivity().getSharedPreferences(
  785. GRID_IS_PREFERED_PREFERENCE, Context.MODE_PRIVATE
  786. );
  787. if (setting.contains(String.valueOf(fileToTest.getFileId()))) {
  788. return setting.getBoolean(String.valueOf(fileToTest.getFileId()), false);
  789. } else {
  790. do {
  791. if (fileToTest.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
  792. parentPath = new File(fileToTest.getRemotePath()).getParent();
  793. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
  794. parentPath + OCFile.PATH_SEPARATOR;
  795. parentDir = storageManager.getFileByPath(parentPath);
  796. } else {
  797. parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);
  798. }
  799. while (parentDir == null) {
  800. parentPath = new File(parentPath).getParent();
  801. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
  802. parentPath + OCFile.PATH_SEPARATOR;
  803. parentDir = storageManager.getFileByPath(parentPath);
  804. }
  805. fileToTest = parentDir;
  806. } while (endWhile(parentDir, setting));
  807. return setting.getBoolean(String.valueOf(fileToTest.getFileId()), false);
  808. }
  809. } else {
  810. return false;
  811. }
  812. }
  813. private boolean endWhile(OCFile parentDir, SharedPreferences setting) {
  814. if (parentDir.getRemotePath().compareToIgnoreCase(OCFile.ROOT_PATH) == 0) {
  815. return false;
  816. } else {
  817. return !setting.contains(String.valueOf(parentDir.getFileId()));
  818. }
  819. }
  820. private void changeGridIcon(Menu menu){
  821. MenuItem menuItem = menu.findItem(R.id.action_switch_view);
  822. if (isGridViewPreferred(mFile)){
  823. menuItem.setTitle(getString(R.string.action_switch_list_view));
  824. menuItem.setIcon(R.drawable.ic_view_list);
  825. } else {
  826. menuItem.setTitle(getString(R.string.action_switch_grid_view));
  827. menuItem.setIcon(R.drawable.ic_view_module);
  828. }
  829. }
  830. public void setListAsPreferred() {
  831. saveGridAsPreferred(false);
  832. switchToListView();
  833. }
  834. public void setGridAsPreferred() {
  835. saveGridAsPreferred(true);
  836. switchToGridView();
  837. }
  838. private void saveGridAsPreferred(boolean setGrid){
  839. SharedPreferences setting = getActivity().getSharedPreferences(
  840. GRID_IS_PREFERED_PREFERENCE, Context.MODE_PRIVATE
  841. );
  842. SharedPreferences.Editor editor = setting.edit();
  843. editor.putBoolean(String.valueOf(mFile.getFileId()), setGrid);
  844. editor.apply();
  845. }
  846. }