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