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