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