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