5386ac8c32d36aa212b908ce07f42362496a0826
[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
82 private OCFile mTargetFile;
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 mFooterView = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
129 R.layout.list_footer, null, false);
130 setFooterView(mFooterView);
131
132 Bundle args = getArguments();
133 boolean justFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false);
134 mAdapter = new FileListListAdapter(
135 justFolders,
136 getSherlockActivity(),
137 mContainerActivity
138 );
139 setListAdapter(mAdapter);
140
141 registerForContextMenu(getGridView());
142 getGridView().setOnCreateContextMenuListener(this);
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 listDirectory(mFile);
189
190 onRefresh(false);
191
192 // restore index and top position
193 restoreIndexAndTopPosition();
194
195 } // else - should never happen now
196
197 return moveCount;
198 }
199
200 @Override
201 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
202 OCFile file = (OCFile) mAdapter.getItem(position);
203 if (file != null) {
204 if (file.isFolder()) {
205 // update state and view of this fragment
206 listDirectory(file);
207 // then, notify parent activity to let it update its state and view
208 mContainerActivity.onBrowsedDownTo(file);
209 // save index and top position
210 saveIndexAndTopPosition(position);
211
212 } else { /// Click on a file
213 if (PreviewImageFragment.canBePreviewed(file)) {
214 // preview image - it handles the download, if needed
215 ((FileDisplayActivity)mContainerActivity).startImagePreview(file);
216
217 } else if (file.isDown()) {
218 if (PreviewMediaFragment.canBePreviewed(file)) {
219 // media preview
220 ((FileDisplayActivity)mContainerActivity).startMediaPreview(file, 0, true);
221 } else {
222 mContainerActivity.getFileOperationsHelper().openFile(file);
223 }
224
225 } else {
226 // automatic download, preview on finish
227 ((FileDisplayActivity)mContainerActivity).startDownloadForPreview(file);
228 }
229
230 }
231
232 } else {
233 Log_OC.d(TAG, "Null object in ListAdapter!!");
234 }
235
236 }
237
238 /**
239 * {@inheritDoc}
240 */
241 @Override
242 public void onCreateContextMenu (
243 ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
244 super.onCreateContextMenu(menu, v, menuInfo);
245 Bundle args = getArguments();
246 boolean allowContextualActions =
247 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
248 if (allowContextualActions) {
249 MenuInflater inflater = getSherlockActivity().getMenuInflater();
250 inflater.inflate(R.menu.file_actions_menu, menu);
251 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
252 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
253
254 if (mContainerActivity.getStorageManager() != null) {
255 FileMenuFilter mf = new FileMenuFilter(
256 targetFile,
257 mContainerActivity.getStorageManager().getAccount(),
258 mContainerActivity,
259 getSherlockActivity()
260 );
261 mf.filter(menu);
262 }
263
264 /// TODO break this direct dependency on FileDisplayActivity... if possible
265 MenuItem item = menu.findItem(R.id.action_open_file_with);
266 FileFragment frag = ((FileDisplayActivity)getSherlockActivity()).getSecondFragment();
267 if (frag != null && frag instanceof FileDetailFragment &&
268 frag.getFile().getFileId() == targetFile.getFileId()) {
269 item = menu.findItem(R.id.action_see_details);
270 if (item != null) {
271 item.setVisible(false);
272 item.setEnabled(false);
273 }
274 }
275 }
276 }
277
278
279 /**
280 * {@inhericDoc}
281 */
282 @Override
283 public boolean onContextItemSelected (MenuItem item) {
284 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
285 mTargetFile = (OCFile) mAdapter.getItem(info.position);
286 switch (item.getItemId()) {
287 case R.id.action_share_file: {
288 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
289 return true;
290 }
291 case R.id.action_open_file_with: {
292 mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
293 return true;
294 }
295 case R.id.action_unshare_file: {
296 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
297 return true;
298 }
299 case R.id.action_rename_file: {
300 RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile);
301 dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
302 return true;
303 }
304 case R.id.action_remove_file: {
305 RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile);
306 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
307 return true;
308 }
309 case R.id.action_download_file:
310 case R.id.action_sync_file: {
311 mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
312 return true;
313 }
314 case R.id.action_cancel_download:
315 case R.id.action_cancel_upload: {
316 ((FileDisplayActivity)mContainerActivity).cancelTransference(mTargetFile);
317 return true;
318 }
319 case R.id.action_see_details: {
320 mContainerActivity.showDetails(mTargetFile);
321 return true;
322 }
323 case R.id.action_send_file: {
324 // Obtain the file
325 if (!mTargetFile.isDown()) { // Download the file
326 Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
327 ((FileDisplayActivity)mContainerActivity).startDownloadForSending(mTargetFile);
328
329 } else {
330 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
331 }
332 return true;
333 }
334 case R.id.action_move: {
335 Intent action = new Intent(getActivity(), FolderPickerActivity.class);
336
337 // Pass mTargetFile that contains info of selected file/folder
338 action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
339 getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES);
340 return true;
341 }
342 default:
343 return super.onContextItemSelected(item);
344 }
345 }
346
347
348 /**
349 * Use this to query the {@link OCFile} that is currently
350 * being displayed by this fragment
351 * @return The currently viewed OCFile
352 */
353 public OCFile getCurrentFile(){
354 return mFile;
355 }
356
357 /**
358 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
359 */
360 public void listDirectory(){
361 listDirectory(null);
362 }
363
364 /**
365 * Lists the given directory on the view. When the input parameter is null,
366 * it will either refresh the last known directory. list the root
367 * if there never was a directory.
368 *
369 * @param directory File to be listed
370 */
371 public void listDirectory(OCFile directory) {
372 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
373 if (storageManager != null) {
374
375 // Check input parameters for null
376 if(directory == null){
377 if(mFile != null){
378 directory = mFile;
379 } else {
380 directory = storageManager.getFileByPath("/");
381 if (directory == null) return; // no files, wait for sync
382 }
383 }
384
385
386 // If that's not a directory -> List its parent
387 if(!directory.isFolder()){
388 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
389 directory = storageManager.getFileById(directory.getParentId());
390 }
391
392 mAdapter.swapDirectory(directory, storageManager);
393 if (mFile == null || !mFile.equals(directory)) {
394 imageView.setSelection(0);
395 }
396 mFile = directory;
397
398 Vector<OCFile> files = storageManager.getFolderContent(directory);
399 // Update Footer
400 TextView footerText = (TextView) mFooterView.findViewById(R.id.footerText);
401 footerText.setText(generateFooterText(directory));
402 if (DisplayUtils.decideViewLayout(files)){
403 switchImageView();
404 } else {
405 switchFileView();
406 }
407 }
408 }
409
410 private String generateFooterText(OCFile directory) {
411 Integer files = 0;
412 Integer folders = 0;
413
414 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
415 Vector<OCFile> mFiles = storageManager.getFolderContent(mFile);
416
417 for (OCFile ocFile : mFiles) {
418 if (ocFile.isFolder()) {
419 folders++;
420 } else {
421 files++;
422 }
423 }
424
425 String output = "";
426
427 if (files > 0){
428 if (files == 1) {
429 output = output + files.toString() + " " + getResources().getString(R.string.file_list_file);
430 } else {
431 output = output + files.toString() + " " + getResources().getString(R.string.file_list_files);
432 }
433 }
434 if (folders > 0 && files > 0){
435 output = output + ", ";
436 }
437 if (folders == 1) {
438 output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folder);
439 } else if (folders > 1) {
440 output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folders);
441 }
442
443 // Fix for showing or not to show the footerView
444 if (folders == 0 && files == 0) { // If no files or folders, remove footerView for allowing
445 // to show the emptyList message
446 removeFooterView(mFooterView);
447 } else { // set a new footerView if there is not one for showing the number or files/folders
448 if (getFooterViewCount()== 0) {
449 ((ViewGroup)mFooterView.getParent()).removeView(mFooterView);
450 setFooterView(mFooterView);
451 }
452 }
453
454 return output;
455 }
456
457 public void sortByName(boolean descending) {
458 mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
459 }
460
461 public void sortByDate(boolean descending) {
462 mAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending);
463 }
464
465 public void sortBySize(boolean descending) {
466 mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
467 }
468
469 }