d243efc74dd1393809e09465e5e0af8525b46246
[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 import java.util.Vector;
22
23 import android.app.Activity;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.support.v4.widget.SwipeRefreshLayout;
28 import android.view.ContextMenu;
29 import android.view.MenuInflater;
30 import android.view.MenuItem;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.AdapterView;
34 import android.widget.AdapterView.AdapterContextMenuInfo;
35 import android.widget.TextView;
36 import android.view.LayoutInflater;
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.DisplayUtils;
53 import com.owncloud.android.utils.FileStorageUtils;
54
55 /**
56 * A Fragment that lists all files and folders in a given path.
57 *
58 * TODO refactorize to get rid of direct dependency on FileDisplayActivity
59 *
60 * @author Bartek Przybylski
61 * @author masensio
62 * @author David A. Velasco
63 */
64 public class OCFileListFragment extends ExtendedListFragment {
65
66 private static final String TAG = OCFileListFragment.class.getSimpleName();
67
68 private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ?
69 OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
70
71 public final static String ARG_JUST_FOLDERS = MY_PACKAGE + ".JUST_FOLDERS";
72 public final static String ARG_ALLOW_CONTEXTUAL_ACTIONS = MY_PACKAGE + ".ALLOW_CONTEXTUAL";
73
74 private static final String KEY_FILE = MY_PACKAGE + ".extra.FILE";
75
76 private FileFragment.ContainerActivity mContainerActivity;
77
78 private OCFile mFile = null;
79 private FileListListAdapter mAdapter;
80 private View mFooterView;
81 private boolean mJustFolders;
82
83 private OCFile mTargetFile;
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 mFooterView = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
130 R.layout.list_footer, null, false);
131 if (mJustFolders) {
132 removeFooterView(mFooterView);
133 } else {
134 setFooterView(mFooterView);
135 }
136
137 Bundle args = getArguments();
138 mJustFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false);
139 mAdapter = new FileListListAdapter(
140 mJustFolders,
141 getSherlockActivity(),
142 mContainerActivity
143 );
144 setListAdapter(mAdapter);
145
146 registerForContextMenu(getGridView());
147 getGridView().setOnCreateContextMenuListener(this);
148 }
149
150 /**
151 * Saves the current listed folder.
152 */
153 @Override
154 public void onSaveInstanceState (Bundle outState) {
155 super.onSaveInstanceState(outState);
156 outState.putParcelable(KEY_FILE, mFile);
157 }
158
159 /**
160 * Call this, when the user presses the up button.
161 *
162 * Tries to move up the current folder one level. If the parent folder was removed from the
163 * database, it continues browsing up until finding an existing folders.
164 *
165 * return Count of folder levels browsed up.
166 */
167 public int onBrowseUp() {
168 OCFile parentDir = null;
169 int moveCount = 0;
170
171 if(mFile != null){
172 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
173
174 String parentPath = null;
175 if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
176 parentPath = new File(mFile.getRemotePath()).getParent();
177 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
178 parentPath + OCFile.PATH_SEPARATOR;
179 parentDir = storageManager.getFileByPath(parentPath);
180 moveCount++;
181 } else {
182 parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);
183 }
184 while (parentDir == null) {
185 parentPath = new File(parentPath).getParent();
186 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
187 parentPath + OCFile.PATH_SEPARATOR;
188 parentDir = storageManager.getFileByPath(parentPath);
189 moveCount++;
190 } // exit is granted because storageManager.getFileByPath("/") never returns null
191 mFile = parentDir;
192
193 listDirectory(mFile);
194
195 onRefresh(false);
196
197 // restore index and top position
198 restoreIndexAndTopPosition();
199
200 } // else - should never happen now
201
202 return moveCount;
203 }
204
205 @Override
206 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
207 OCFile file = (OCFile) mAdapter.getItem(position);
208 if (file != null) {
209 if (file.isFolder()) {
210 // update state and view of this fragment
211 listDirectory(file);
212 // then, notify parent activity to let it update its state and view
213 mContainerActivity.onBrowsedDownTo(file);
214 // save index and top position
215 saveIndexAndTopPosition(position);
216
217 } else { /// Click on a file
218 if (PreviewImageFragment.canBePreviewed(file)) {
219 // preview image - it handles the download, if needed
220 ((FileDisplayActivity)mContainerActivity).startImagePreview(file);
221
222 } else if (file.isDown()) {
223 if (PreviewMediaFragment.canBePreviewed(file)) {
224 // media preview
225 ((FileDisplayActivity)mContainerActivity).startMediaPreview(file, 0, true);
226 } else {
227 mContainerActivity.getFileOperationsHelper().openFile(file);
228 }
229
230 } else {
231 // automatic download, preview on finish
232 ((FileDisplayActivity)mContainerActivity).startDownloadForPreview(file);
233 }
234
235 }
236
237 } else {
238 Log_OC.d(TAG, "Null object in ListAdapter!!");
239 }
240
241 }
242
243 /**
244 * {@inheritDoc}
245 */
246 @Override
247 public void onCreateContextMenu (
248 ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
249 super.onCreateContextMenu(menu, v, menuInfo);
250 Bundle args = getArguments();
251 boolean allowContextualActions =
252 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
253 if (allowContextualActions) {
254 MenuInflater inflater = getSherlockActivity().getMenuInflater();
255 inflater.inflate(R.menu.file_actions_menu, menu);
256 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
257 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
258
259 if (mContainerActivity.getStorageManager() != null) {
260 FileMenuFilter mf = new FileMenuFilter(
261 targetFile,
262 mContainerActivity.getStorageManager().getAccount(),
263 mContainerActivity,
264 getSherlockActivity()
265 );
266 mf.filter(menu);
267 }
268
269 /// TODO break this direct dependency on FileDisplayActivity... if possible
270 MenuItem item = menu.findItem(R.id.action_open_file_with);
271 FileFragment frag = ((FileDisplayActivity)getSherlockActivity()).getSecondFragment();
272 if (frag != null && frag instanceof FileDetailFragment &&
273 frag.getFile().getFileId() == targetFile.getFileId()) {
274 item = menu.findItem(R.id.action_see_details);
275 if (item != null) {
276 item.setVisible(false);
277 item.setEnabled(false);
278 }
279 }
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 default:
348 return super.onContextItemSelected(item);
349 }
350 }
351
352
353 /**
354 * Use this to query the {@link OCFile} that is currently
355 * being displayed by this fragment
356 * @return The currently viewed OCFile
357 */
358 public OCFile getCurrentFile(){
359 return mFile;
360 }
361
362 /**
363 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
364 */
365 public void listDirectory(){
366 listDirectory(null);
367 }
368
369 /**
370 * Lists the given directory on the view. When the input parameter is null,
371 * it will either refresh the last known directory. list the root
372 * if there never was a directory.
373 *
374 * @param directory File to be listed
375 */
376 public void listDirectory(OCFile directory) {
377 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
378 if (storageManager != null) {
379
380 // Check input parameters for null
381 if(directory == null){
382 if(mFile != null){
383 directory = mFile;
384 } else {
385 directory = storageManager.getFileByPath("/");
386 if (directory == null) return; // no files, wait for sync
387 }
388 }
389
390
391 // If that's not a directory -> List its parent
392 if(!directory.isFolder()){
393 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
394 directory = storageManager.getFileById(directory.getParentId());
395 }
396
397 mAdapter.swapDirectory(directory, storageManager);
398 if (mFile == null || !mFile.equals(directory)) {
399 imageView.setSelection(0);
400 }
401 mFile = directory;
402
403 Vector<OCFile> files = storageManager.getFolderContent(directory);
404 // Update Footer
405 TextView footerText = (TextView) mFooterView.findViewById(R.id.footerText);
406 footerText.setText(generateFooterText(directory));
407 if (DisplayUtils.decideViewLayout(files) && !mJustFolders){
408 switchImageView();
409 } else {
410 switchFileView();
411 }
412 }
413 }
414
415 private String generateFooterText(OCFile directory) {
416 Integer files = 0;
417 Integer folders = 0;
418
419 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
420 Vector<OCFile> mFiles = storageManager.getFolderContent(mFile);
421
422 for (OCFile ocFile : mFiles) {
423 if (ocFile.isFolder()) {
424 folders++;
425 } else {
426 files++;
427 }
428 }
429
430 String output = "";
431
432 if (files > 0){
433 if (files == 1) {
434 output = output + files.toString() + " " + getResources().getString(R.string.file_list_file);
435 } else {
436 output = output + files.toString() + " " + getResources().getString(R.string.file_list_files);
437 }
438 }
439 if (folders > 0 && files > 0){
440 output = output + ", ";
441 }
442 if (folders == 1) {
443 output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folder);
444 } else if (folders > 1) {
445 output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folders);
446 }
447
448 // Fix for showing or not to show the footerView
449 if (mJustFolders || (folders == 0 && files == 0)) {
450 // If fragment to choose target folder for a MOVE or
451 // no files or folders,
452 // remove footerView for allowing to show the emptyList message
453 removeFooterView(mFooterView);
454 } else { // set a new footerView if there is not one for showing the number or files/folders
455 if (getFooterViewCount()== 0) {
456 ((ViewGroup)mFooterView.getParent()).removeView(mFooterView);
457 setFooterView(mFooterView);
458 }
459 }
460
461 return output;
462 }
463
464 public void sortByName(boolean descending) {
465 mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
466 }
467
468 public void sortByDate(boolean descending) {
469 mAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending);
470 }
471
472 public void sortBySize(boolean descending) {
473 mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
474 }
475
476 }