9430f112fe2a92d91fac61948f170d3e150f9b3b
[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.LayoutInflater;
33 import android.view.MenuInflater;
34 import android.view.MenuItem;
35 import android.view.View;
36 import android.widget.AdapterView;
37 import android.widget.AdapterView.AdapterContextMenuInfo;
38 import android.widget.TextView;
39
40 import com.owncloud.android.MainApp;
41 import com.owncloud.android.R;
42 import com.owncloud.android.datamodel.FileDataStorageManager;
43 import com.owncloud.android.datamodel.OCFile;
44 import com.owncloud.android.files.FileMenuFilter;
45 import com.owncloud.android.lib.common.utils.Log_OC;
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 refactorize 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 final static Double THUMBNAIL_THRESHOLD = 0.5;
75
76 private FileFragment.ContainerActivity mContainerActivity;
77
78 private OCFile mFile = null;
79 private FileListListAdapter mAdapter;
80 private boolean mJustFolders;
81
82 private OCFile mTargetFile;
83
84
85
86 /**
87 * {@inheritDoc}
88 */
89 @Override
90 public void onAttach(Activity activity) {
91 super.onAttach(activity);
92 Log_OC.e(TAG, "onAttach");
93 try {
94 mContainerActivity = (FileFragment.ContainerActivity) activity;
95
96 } catch (ClassCastException e) {
97 throw new ClassCastException(activity.toString() + " must implement " +
98 FileFragment.ContainerActivity.class.getSimpleName());
99 }
100 try {
101 setOnRefreshListener((OnEnforceableRefreshListener) activity);
102
103 } catch (ClassCastException e) {
104 throw new ClassCastException(activity.toString() + " must implement " +
105 SwipeRefreshLayout.OnRefreshListener.class.getSimpleName());
106 }
107 }
108
109
110 @Override
111 public void onDetach() {
112 setOnRefreshListener(null);
113 mContainerActivity = null;
114 super.onDetach();
115 }
116
117 /**
118 * {@inheritDoc}
119 */
120 @Override
121 public void onActivityCreated(Bundle savedInstanceState) {
122 super.onActivityCreated(savedInstanceState);
123 Log_OC.e(TAG, "onActivityCreated() start");
124
125 if (savedInstanceState != null) {
126 mFile = savedInstanceState.getParcelable(KEY_FILE);
127 }
128
129 if (mJustFolders) {
130 setFooterEnabled(false);
131 } else {
132 setFooterEnabled(true);
133 }
134
135 Bundle args = getArguments();
136 mJustFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false);
137 mAdapter = new FileListListAdapter(
138 mJustFolders,
139 getSherlockActivity(),
140 mContainerActivity
141 );
142 setListAdapter(mAdapter);
143
144 registerForContextMenu();
145 }
146
147 /**
148 * Saves the current listed folder.
149 */
150 @Override
151 public void onSaveInstanceState (Bundle outState) {
152 super.onSaveInstanceState(outState);
153 outState.putParcelable(KEY_FILE, mFile);
154 }
155
156 /**
157 * Call this, when the user presses the up button.
158 *
159 * Tries to move up the current folder one level. If the parent folder was removed from the
160 * database, it continues browsing up until finding an existing folders.
161 *
162 * return Count of folder levels browsed up.
163 */
164 public int onBrowseUp() {
165 OCFile parentDir = null;
166 int moveCount = 0;
167
168 if(mFile != null){
169 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
170
171 String parentPath = null;
172 if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
173 parentPath = new File(mFile.getRemotePath()).getParent();
174 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
175 parentPath + OCFile.PATH_SEPARATOR;
176 parentDir = storageManager.getFileByPath(parentPath);
177 moveCount++;
178 } else {
179 parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);
180 }
181 while (parentDir == null) {
182 parentPath = new File(parentPath).getParent();
183 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
184 parentPath + OCFile.PATH_SEPARATOR;
185 parentDir = storageManager.getFileByPath(parentPath);
186 moveCount++;
187 } // exit is granted because storageManager.getFileByPath("/") never returns null
188 mFile = parentDir;
189
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 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 = getSherlockActivity().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 getSherlockActivity()
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)getSherlockActivity()).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 }
279
280
281 /**
282 * {@inhericDoc}
283 */
284 @Override
285 public boolean onContextItemSelected (MenuItem item) {
286 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
287 mTargetFile = (OCFile) mAdapter.getItem(info.position);
288 switch (item.getItemId()) {
289 case R.id.action_share_file: {
290 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
291 return true;
292 }
293 case R.id.action_open_file_with: {
294 mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
295 return true;
296 }
297 case R.id.action_unshare_file: {
298 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
299 return true;
300 }
301 case R.id.action_rename_file: {
302 RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile);
303 dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
304 return true;
305 }
306 case R.id.action_remove_file: {
307 RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile);
308 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
309 return true;
310 }
311 case R.id.action_download_file:
312 case R.id.action_sync_file: {
313 mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
314 return true;
315 }
316 case R.id.action_cancel_download:
317 case R.id.action_cancel_upload: {
318 ((FileDisplayActivity)mContainerActivity).cancelTransference(mTargetFile);
319 return true;
320 }
321 case R.id.action_see_details: {
322 mContainerActivity.showDetails(mTargetFile);
323 return true;
324 }
325 case R.id.action_send_file: {
326 // Obtain the file
327 if (!mTargetFile.isDown()) { // Download the file
328 Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
329 ((FileDisplayActivity)mContainerActivity).startDownloadForSending(mTargetFile);
330
331 } else {
332 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
333 }
334 return true;
335 }
336 case R.id.action_move: {
337 Intent action = new Intent(getActivity(), FolderPickerActivity.class);
338
339 // Pass mTargetFile that contains info of selected file/folder
340 action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
341 getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES);
342 return true;
343 }
344 default:
345 return super.onContextItemSelected(item);
346 }
347 }
348
349
350 /**
351 * Use this to query the {@link OCFile} that is currently
352 * being displayed by this fragment
353 * @return The currently viewed OCFile
354 */
355 public OCFile getCurrentFile(){
356 return mFile;
357 }
358
359 /**
360 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
361 */
362 public void listDirectory(boolean onlyOnDevice){
363 listDirectory(null, onlyOnDevice);
364 }
365
366 public void refreshDirectory(){
367 listDirectory(getCurrentFile(), MainApp.getOnlyOnDevice());
368 }
369
370 /**
371 * Lists the given directory on the view. When the input parameter is null,
372 * it will either refresh the last known directory. list the root
373 * if there never was a directory.
374 *
375 * @param directory File to be listed
376 */
377 public void listDirectory(OCFile directory, boolean onlyOnDevice) {
378 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
379 if (storageManager != null) {
380
381 // Check input parameters for null
382 if(directory == null){
383 if(mFile != null){
384 directory = mFile;
385 } else {
386 directory = storageManager.getFileByPath("/");
387 if (directory == null) return; // no files, wait for sync
388 }
389 }
390
391
392 // If that's not a directory -> List its parent
393 if(!directory.isFolder()){
394 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
395 directory = storageManager.getFileById(directory.getParentId());
396 }
397
398 mAdapter.swapDirectory(directory, storageManager, onlyOnDevice);
399 if (mFile == null || !mFile.equals(directory)) {
400 mCurrentListView.setSelection(0);
401 }
402 mFile = directory;
403
404 updateLayout();
405
406 }
407 }
408
409 private void updateLayout() {
410 if (!mJustFolders) {
411 int filesCount = 0, foldersCount = 0, imagesCount = 0;
412 int count = mAdapter.getCount();
413 OCFile file;
414 for (int i=0; i < count ; i++) {
415 file = (OCFile) mAdapter.getItem(i);
416 if (file.isFolder()) {
417 foldersCount++;
418 } else {
419 filesCount++;
420 if (file.isImage()){
421 imagesCount++;
422 }
423 }
424 }
425 // set footer text
426 setFooterText(generateFooterText(filesCount, foldersCount));
427
428 // decide grid vs list view
429 if (((double)imagesCount / (double)filesCount) >= THUMBNAIL_THRESHOLD) {
430 switchToGridView();
431 } else {
432 switchToListView();
433 }
434 }
435 }
436
437 private String generateFooterText(int filesCount, int foldersCount) {
438 String output = "";
439 if (filesCount > 0){
440 if (filesCount == 1) {
441 output = output + filesCount + " " + getResources().getString(R.string.file_list_file);
442 } else {
443 output = output + filesCount + " " + getResources().getString(R.string.file_list_files);
444 }
445 }
446 if (foldersCount > 0 && filesCount > 0){
447 output = output + ", ";
448 }
449 if (foldersCount == 1) {
450 output = output + foldersCount + " " + getResources().getString(R.string.file_list_folder);
451 } else if (foldersCount > 1) {
452 output = output + foldersCount + " " + getResources().getString(R.string.file_list_folders);
453 }
454
455 return output;
456 }
457
458
459 public void sortByName(boolean descending) {
460 mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
461 }
462
463 public void sortByDate(boolean descending) {
464 mAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending);
465 }
466
467 public void sortBySize(boolean descending) {
468 mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
469 }
470
471
472
473 }