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