f336bd71e72614c7370449d8b038f7736b754998
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / OCFileListFragment.java
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) 2015 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
25 import android.app.Activity;
26 import android.content.Intent;
27 import android.os.Bundle;
28 import android.support.v4.widget.SwipeRefreshLayout;
29 import android.view.ContextMenu;
30 import android.view.Menu;
31 import android.view.MenuInflater;
32 import android.view.MenuItem;
33 import android.view.View;
34 import android.widget.AdapterView;
35 import android.widget.AdapterView.AdapterContextMenuInfo;
36 import android.widget.PopupMenu;
37
38 import com.owncloud.android.R;
39 import com.owncloud.android.authentication.AccountUtils;
40 import com.owncloud.android.datamodel.FileDataStorageManager;
41 import com.owncloud.android.datamodel.OCFile;
42 import com.owncloud.android.files.FileMenuFilter;
43 import com.owncloud.android.lib.common.utils.Log_OC;
44 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
45 import com.owncloud.android.ui.activity.FileActivity;
46 import com.owncloud.android.ui.activity.FileDisplayActivity;
47 import com.owncloud.android.ui.activity.FolderPickerActivity;
48 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
49 import com.owncloud.android.ui.adapter.FileListListAdapter;
50 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
51 import com.owncloud.android.ui.dialog.FileActionsDialogFragment;
52 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
53 import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
54 import com.owncloud.android.ui.preview.PreviewImageFragment;
55 import com.owncloud.android.ui.preview.PreviewMediaFragment;
56 import com.owncloud.android.utils.FileStorageUtils;
57 import com.owncloud.android.ui.preview.PreviewTextFragment;
58
59 import java.io.File;
60
61 /**
62 * A Fragment that lists all files and folders in a given path.
63 *
64 * TODO refactor to get rid of direct dependency on FileDisplayActivity
65 */
66 public class OCFileListFragment extends ExtendedListFragment implements FileActionsDialogFragment.FileActionsDialogFragmentListener {
67
68 private static final String TAG = OCFileListFragment.class.getSimpleName();
69
70 private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ?
71 OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
72
73 public final static String ARG_JUST_FOLDERS = MY_PACKAGE + ".JUST_FOLDERS";
74 public final static String ARG_ALLOW_CONTEXTUAL_ACTIONS = MY_PACKAGE + ".ALLOW_CONTEXTUAL";
75
76 private static final String KEY_FILE = MY_PACKAGE + ".extra.FILE";
77
78 private FileFragment.ContainerActivity mContainerActivity;
79
80 private OCFile mFile = null;
81 private FileListListAdapter mAdapter;
82 private boolean mJustFolders;
83
84 private OCFile mTargetFile;
85
86
87
88 /**
89 * {@inheritDoc}
90 */
91 @Override
92 public void onAttach(Activity activity) {
93 super.onAttach(activity);
94 Log_OC.e(TAG, "onAttach");
95 try {
96 mContainerActivity = (FileFragment.ContainerActivity) activity;
97
98 } catch (ClassCastException e) {
99 throw new ClassCastException(activity.toString() + " must implement " +
100 FileFragment.ContainerActivity.class.getSimpleName());
101 }
102 try {
103 setOnRefreshListener((OnEnforceableRefreshListener) activity);
104
105 } catch (ClassCastException e) {
106 throw new ClassCastException(activity.toString() + " must implement " +
107 SwipeRefreshLayout.OnRefreshListener.class.getSimpleName());
108 }
109 }
110
111
112 @Override
113 public void onDetach() {
114 setOnRefreshListener(null);
115 mContainerActivity = null;
116 super.onDetach();
117 }
118
119 /**
120 * {@inheritDoc}
121 */
122 @Override
123 public void onActivityCreated(Bundle savedInstanceState) {
124 super.onActivityCreated(savedInstanceState);
125 Log_OC.e(TAG, "onActivityCreated() start");
126
127 if (savedInstanceState != null) {
128 mFile = savedInstanceState.getParcelable(KEY_FILE);
129 }
130
131 if (mJustFolders) {
132 setFooterEnabled(false);
133 } else {
134 setFooterEnabled(true);
135 }
136
137 Bundle args = getArguments();
138 mJustFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false);
139 mAdapter = new FileListListAdapter(
140 mJustFolders,
141 getActivity(),
142 mContainerActivity
143 );
144 setListAdapter(mAdapter);
145
146 registerLongClickListener();
147 }
148
149 private void registerLongClickListener() {
150 getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
151 public boolean onItemLongClick(AdapterView<?> arg0, View v,
152 int index, long arg3) {
153 showFileAction(index);
154 return true;
155 }
156 });
157 }
158
159
160 private void showFileAction(int fileIndex) {
161 Bundle args = getArguments();
162 PopupMenu pm = new PopupMenu(getActivity(),null);
163 Menu menu = pm.getMenu();
164
165 boolean allowContextualActions =
166 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
167
168 if (allowContextualActions) {
169 MenuInflater inflater = getActivity().getMenuInflater();
170
171 inflater.inflate(R.menu.file_actions_menu, menu);
172 OCFile targetFile = (OCFile) mAdapter.getItem(fileIndex);
173
174 if (mContainerActivity.getStorageManager() != null) {
175 FileMenuFilter mf = new FileMenuFilter(
176 targetFile,
177 mContainerActivity.getStorageManager().getAccount(),
178 mContainerActivity,
179 getActivity()
180 );
181 mf.filter(menu);
182 }
183
184 /// TODO break this direct dependency on FileDisplayActivity... if possible
185 MenuItem item = menu.findItem(R.id.action_open_file_with);
186 FileFragment frag = ((FileDisplayActivity)getActivity()).getSecondFragment();
187 if (frag != null && frag instanceof FileDetailFragment &&
188 frag.getFile().getFileId() == targetFile.getFileId()) {
189 item = menu.findItem(R.id.action_see_details);
190 if (item != null) {
191 item.setVisible(false);
192 item.setEnabled(false);
193 }
194 }
195
196 FileActionsDialogFragment dialog = FileActionsDialogFragment.newInstance(menu, fileIndex, targetFile.getFileName());
197 dialog.setTargetFragment(this, 0);
198 dialog.show(getFragmentManager(), FileActionsDialogFragment.FTAG_FILE_ACTIONS);
199 }
200 }
201
202 /**
203 * Saves the current listed folder.
204 */
205 @Override
206 public void onSaveInstanceState(Bundle outState) {
207 super.onSaveInstanceState(outState);
208 outState.putParcelable(KEY_FILE, mFile);
209 }
210
211 /**
212 * Call this, when the user presses the up button.
213 *
214 * Tries to move up the current folder one level. If the parent folder was removed from the
215 * database, it continues browsing up until finding an existing folders.
216 * <p/>
217 * return Count of folder levels browsed up.
218 */
219 public int onBrowseUp() {
220 OCFile parentDir = null;
221 int moveCount = 0;
222
223 if (mFile != null) {
224 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
225
226 String parentPath = null;
227 if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
228 parentPath = new File(mFile.getRemotePath()).getParent();
229 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
230 parentPath + OCFile.PATH_SEPARATOR;
231 parentDir = storageManager.getFileByPath(parentPath);
232 moveCount++;
233 } else {
234 parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);
235 }
236 while (parentDir == null) {
237 parentPath = new File(parentPath).getParent();
238 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
239 parentPath + OCFile.PATH_SEPARATOR;
240 parentDir = storageManager.getFileByPath(parentPath);
241 moveCount++;
242 } // exit is granted because storageManager.getFileByPath("/") never returns null
243 mFile = parentDir;
244
245 // TODO Enable when "On Device" is recovered ?
246 listDirectory(mFile /*, MainApp.getOnlyOnDevice()*/);
247
248 onRefresh(false);
249
250 // restore index and top position
251 restoreIndexAndTopPosition();
252
253 } // else - should never happen now
254
255 return moveCount;
256 }
257
258 @Override
259 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
260 OCFile file = (OCFile) mAdapter.getItem(position);
261 if (file != null) {
262 if (file.isFolder()) {
263 // update state and view of this fragment
264 // TODO Enable when "On Device" is recovered ?
265 listDirectory(file/*, MainApp.getOnlyOnDevice()*/);
266 // then, notify parent activity to let it update its state and view
267 mContainerActivity.onBrowsedDownTo(file);
268 // save index and top position
269 saveIndexAndTopPosition(position);
270
271 } else { /// Click on a file
272 if (PreviewImageFragment.canBePreviewed(file)) {
273 // preview image - it handles the download, if needed
274 ((FileDisplayActivity)mContainerActivity).startImagePreview(file);
275 } else if (PreviewTextFragment.canBePreviewed(file)){
276 ((FileDisplayActivity)mContainerActivity).startTextPreview(file);
277 } else if (file.isDown()) {
278 if (PreviewMediaFragment.canBePreviewed(file)) {
279 // media preview
280 ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true);
281 } else {
282 mContainerActivity.getFileOperationsHelper().openFile(file);
283 }
284
285 } else {
286 // automatic download, preview on finish
287 ((FileDisplayActivity) mContainerActivity).startDownloadForPreview(file);
288 }
289
290 }
291
292 } else {
293 Log_OC.d(TAG, "Null object in ListAdapter!!");
294 }
295
296 }
297
298 /**
299 * {@inheritDoc}
300 */
301 @Override
302 public void onCreateContextMenu(
303 ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
304 Bundle args = getArguments();
305 boolean allowContextualActions =
306 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
307 if (allowContextualActions) {
308 MenuInflater inflater = getActivity().getMenuInflater();
309 inflater.inflate(R.menu.file_actions_menu, menu);
310 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
311 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
312
313 if (mContainerActivity.getStorageManager() != null) {
314 FileMenuFilter mf = new FileMenuFilter(
315 targetFile,
316 mContainerActivity.getStorageManager().getAccount(),
317 mContainerActivity,
318 getActivity()
319 );
320 mf.filter(menu);
321 }
322
323 /// TODO break this direct dependency on FileDisplayActivity... if possible
324 MenuItem item = menu.findItem(R.id.action_open_file_with);
325 FileFragment frag = ((FileDisplayActivity)getActivity()).getSecondFragment();
326 if (frag != null && frag instanceof FileDetailFragment &&
327 frag.getFile().getFileId() == targetFile.getFileId()) {
328 item = menu.findItem(R.id.action_see_details);
329 if (item != null) {
330 item.setVisible(false);
331 item.setEnabled(false);
332 }
333 }
334 }
335 }
336
337 /**
338 * {@inheritDoc}
339 */
340 @Override
341 public boolean onFileActionChosen(int menuId, int filePosition) {
342 mTargetFile = (OCFile) mAdapter.getItem(filePosition);
343 switch (menuId) {
344 case R.id.action_share_file: {
345 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
346 return true;
347 }
348 case R.id.action_open_file_with: {
349 mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
350 return true;
351 }
352 case R.id.action_unshare_file: {
353 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
354 return true;
355 }
356 case R.id.action_rename_file: {
357 RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile);
358 dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
359 return true;
360 }
361 case R.id.action_remove_file: {
362 RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile);
363 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
364 return true;
365 }
366 case R.id.action_download_file: {
367 mContainerActivity.getFileOperationsHelper().downloadFile(mTargetFile);
368 return true;
369 }
370 case R.id.action_sync_file: {
371 mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
372 return true;
373 }
374 case R.id.action_cancel_download:
375 case R.id.action_cancel_upload: {
376 ((FileDisplayActivity) mContainerActivity).cancelTransference(mTargetFile);
377 return true;
378 }
379 case R.id.action_see_details: {
380 mContainerActivity.showDetails(mTargetFile);
381 return true;
382 }
383 case R.id.action_send_file: {
384 // Obtain the file
385 if (!mTargetFile.isDown()) { // Download the file
386 Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
387 ((FileDisplayActivity) mContainerActivity).startDownloadForSending(mTargetFile);
388
389 } else {
390 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
391 }
392 return true;
393 }
394 case R.id.action_move: {
395 Intent action = new Intent(getActivity(), FolderPickerActivity.class);
396
397 // Pass mTargetFile that contains info of selected file/folder
398 action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
399 getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES);
400 return true;
401 }
402 case R.id.action_favorite_file: {
403 mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, true);
404 return true;
405 }
406 case R.id.action_unfavorite_file: {
407 mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, false);
408 return true;
409 }
410 case R.id.action_copy:
411 Intent action = new Intent(getActivity(), FolderPickerActivity.class);
412
413 // Pass mTargetFile that contains info of selected file/folder
414 action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
415 getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_COPY_FILES);
416 return true;
417 default:
418 return false;
419 }
420 }
421
422 /**
423 * {@inhericDoc}
424 */
425 @Override
426 public boolean onContextItemSelected (MenuItem item) {
427 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
428 boolean matched = onFileActionChosen(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).position);
429 if(!matched) {
430 return super.onContextItemSelected(item);
431 } else {
432 return matched;
433 }
434 }
435
436
437 /**
438 * Use this to query the {@link OCFile} that is currently
439 * being displayed by this fragment
440 *
441 * @return The currently viewed OCFile
442 */
443 public OCFile getCurrentFile() {
444 return mFile;
445 }
446
447 /**
448 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
449 */
450 public void listDirectory(/*boolean onlyOnDevice*/){
451 listDirectory(null);
452 // TODO Enable when "On Device" is recovered ?
453 // listDirectory(null, onlyOnDevice);
454 }
455
456 public void refreshDirectory(){
457 // TODO Enable when "On Device" is recovered ?
458 listDirectory(getCurrentFile()/*, MainApp.getOnlyOnDevice()*/);
459 }
460
461 /**
462 * Lists the given directory on the view. When the input parameter is null,
463 * it will either refresh the last known directory. list the root
464 * if there never was a directory.
465 *
466 * @param directory File to be listed
467 */
468 public void listDirectory(OCFile directory/*, boolean onlyOnDevice*/) {
469 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
470 if (storageManager != null) {
471
472 // Check input parameters for null
473 if (directory == null) {
474 if (mFile != null) {
475 directory = mFile;
476 } else {
477 directory = storageManager.getFileByPath("/");
478 if (directory == null) return; // no files, wait for sync
479 }
480 }
481
482
483 // If that's not a directory -> List its parent
484 if (!directory.isFolder()) {
485 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
486 directory = storageManager.getFileById(directory.getParentId());
487 }
488
489 // TODO Enable when "On Device" is recovered ?
490 mAdapter.swapDirectory(directory, storageManager/*, onlyOnDevice*/);
491 if (mFile == null || !mFile.equals(directory)) {
492 mCurrentListView.setSelection(0);
493 }
494 mFile = directory;
495
496 updateLayout();
497
498 }
499 }
500
501 private void updateLayout() {
502 if (!mJustFolders) {
503 int filesCount = 0, foldersCount = 0, imagesCount = 0;
504 int count = mAdapter.getCount();
505 OCFile file;
506 for (int i=0; i < count ; i++) {
507 file = (OCFile) mAdapter.getItem(i);
508 if (file.isFolder()) {
509 foldersCount++;
510 } else {
511 if (!file.isHidden()) {
512 filesCount++;
513
514 if (file.isImage()) {
515 imagesCount++;
516 }
517 }
518 }
519 }
520 // set footer text
521 setFooterText(generateFooterText(filesCount, foldersCount));
522
523 // decide grid vs list view
524 OwnCloudVersion version = AccountUtils.getServerVersion(
525 ((FileActivity)mContainerActivity).getAccount());
526 if (version != null && version.supportsRemoteThumbnails() &&
527 imagesCount > 0 && imagesCount == filesCount) {
528 switchToGridView();
529 registerLongClickListener();
530 } else {
531 switchToListView();
532 }
533 }
534 }
535
536 private String generateFooterText(int filesCount, int foldersCount) {
537 String output;
538 if (filesCount <= 0) {
539 if (foldersCount <= 0) {
540 output = "";
541
542 } else if (foldersCount == 1) {
543 output = getResources().getString(R.string.file_list__footer__folder);
544
545 } else { // foldersCount > 1
546 output = getResources().getString(R.string.file_list__footer__folders, foldersCount);
547 }
548
549 } else if (filesCount == 1) {
550 if (foldersCount <= 0) {
551 output = getResources().getString(R.string.file_list__footer__file);
552
553 } else if (foldersCount == 1) {
554 output = getResources().getString(R.string.file_list__footer__file_and_folder);
555
556 } else { // foldersCount > 1
557 output = getResources().getString(R.string.file_list__footer__file_and_folders, foldersCount);
558 }
559 } else { // filesCount > 1
560 if (foldersCount <= 0) {
561 output = getResources().getString(R.string.file_list__footer__files, filesCount);
562
563 } else if (foldersCount == 1) {
564 output = getResources().getString(R.string.file_list__footer__files_and_folder, filesCount);
565
566 } else { // foldersCount > 1
567 output = getResources().getString(
568 R.string.file_list__footer__files_and_folders, filesCount, foldersCount
569 );
570
571 }
572 }
573 return output;
574 }
575
576 public void sortByName(boolean descending) {
577 mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
578 }
579
580 public void sortByDate(boolean descending) {
581 mAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending);
582 }
583
584 public void sortBySize(boolean descending) {
585 mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
586 }
587 }