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