update
[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.LayoutInflater;
30 import android.view.MenuInflater;
31 import android.view.MenuItem;
32 import android.view.View;
33 import android.widget.AdapterView;
34 import android.widget.AdapterView.AdapterContextMenuInfo;
35 import android.widget.TextView;
36
37 import com.owncloud.android.MainApp;
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
53 /**
54 * A Fragment that lists all files and folders in a given path.
55 *
56 * TODO refactorize to get rid of direct dependency on FileDisplayActivity
57 *
58 * @author Bartek Przybylski
59 * @author masensio
60 * @author David A. Velasco
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 FileFragment.ContainerActivity mContainerActivity;
75
76 private OCFile mFile = null;
77 private FileListListAdapter mAdapter;
78 private View mFooterView;
79
80 private OCFile mTargetFile;
81
82
83
84 /**
85 * {@inheritDoc}
86 */
87 @Override
88 public void onAttach(Activity activity) {
89 super.onAttach(activity);
90 Log_OC.e(TAG, "onAttach");
91 try {
92 mContainerActivity = (FileFragment.ContainerActivity) activity;
93
94 } catch (ClassCastException e) {
95 throw new ClassCastException(activity.toString() + " must implement " +
96 FileFragment.ContainerActivity.class.getSimpleName());
97 }
98 try {
99 setOnRefreshListener((OnEnforceableRefreshListener) activity);
100
101 } catch (ClassCastException e) {
102 throw new ClassCastException(activity.toString() + " must implement " +
103 SwipeRefreshLayout.OnRefreshListener.class.getSimpleName());
104 }
105 }
106
107
108 @Override
109 public void onDetach() {
110 setOnRefreshListener(null);
111 mContainerActivity = null;
112 super.onDetach();
113 }
114
115 /**
116 * {@inheritDoc}
117 */
118 @Override
119 public void onActivityCreated(Bundle savedInstanceState) {
120 super.onActivityCreated(savedInstanceState);
121 Log_OC.e(TAG, "onActivityCreated() start");
122
123 if (savedInstanceState != null) {
124 mFile = savedInstanceState.getParcelable(KEY_FILE);
125 }
126
127 mFooterView = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
128 R.layout.list_footer, null, false);
129 setFooterView(mFooterView);
130
131 Bundle args = getArguments();
132 boolean justFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false);
133 mAdapter = new FileListListAdapter(
134 justFolders,
135 getSherlockActivity(),
136 mContainerActivity
137 );
138 setListAdapter(mAdapter);
139
140 registerForContextMenu(getListView());
141 getListView().setOnCreateContextMenuListener(this);
142 }
143
144 /**
145 * Saves the current listed folder.
146 */
147 @Override
148 public void onSaveInstanceState (Bundle outState) {
149 super.onSaveInstanceState(outState);
150 outState.putParcelable(KEY_FILE, mFile);
151 }
152
153 /**
154 * Call this, when the user presses the up button.
155 *
156 * Tries to move up the current folder one level. If the parent folder was removed from the
157 * database, it continues browsing up until finding an existing folders.
158 *
159 * return Count of folder levels browsed up.
160 */
161 public int onBrowseUp() {
162 OCFile parentDir = null;
163 int moveCount = 0;
164
165 if(mFile != null){
166 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
167
168 String parentPath = null;
169 if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
170 parentPath = new File(mFile.getRemotePath()).getParent();
171 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
172 parentPath + OCFile.PATH_SEPARATOR;
173 parentDir = storageManager.getFileByPath(parentPath);
174 moveCount++;
175 } else {
176 parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);
177 }
178 while (parentDir == null) {
179 parentPath = new File(parentPath).getParent();
180 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
181 parentPath + OCFile.PATH_SEPARATOR;
182 parentDir = storageManager.getFileByPath(parentPath);
183 moveCount++;
184 } // exit is granted because storageManager.getFileByPath("/") never returns null
185 mFile = parentDir;
186
187 listDirectory(mFile, MainApp.getOnlyOnDevice());
188
189 onRefresh(false);
190
191 // restore index and top position
192 restoreIndexAndTopPosition();
193
194 } // else - should never happen now
195
196 return moveCount;
197 }
198
199 @Override
200 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
201 OCFile file = (OCFile) mAdapter.getItem(position);
202 if (file != null) {
203 if (file.isFolder()) {
204 // update state and view of this fragment
205 listDirectory(file, MainApp.getOnlyOnDevice());
206 // then, notify parent activity to let it update its state and view
207 mContainerActivity.onBrowsedDownTo(file);
208 // save index and top position
209 saveIndexAndTopPosition(position);
210
211 } else { /// Click on a file
212 if (PreviewImageFragment.canBePreviewed(file)) {
213 // preview image - it handles the download, if needed
214 ((FileDisplayActivity)mContainerActivity).startImagePreview(file);
215
216 } else if (file.isDown()) {
217 if (PreviewMediaFragment.canBePreviewed(file)) {
218 // media preview
219 ((FileDisplayActivity)mContainerActivity).startMediaPreview(file, 0, true);
220 } else {
221 mContainerActivity.getFileOperationsHelper().openFile(file);
222 }
223
224 } else {
225 // automatic download, preview on finish
226 ((FileDisplayActivity)mContainerActivity).startDownloadForPreview(file);
227 }
228
229 }
230
231 } else {
232 Log_OC.d(TAG, "Null object in ListAdapter!!");
233 }
234
235 }
236
237 /**
238 * {@inheritDoc}
239 */
240 @Override
241 public void onCreateContextMenu (
242 ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
243 super.onCreateContextMenu(menu, v, menuInfo);
244 Bundle args = getArguments();
245 boolean allowContextualActions =
246 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
247 if (allowContextualActions) {
248 MenuInflater inflater = getSherlockActivity().getMenuInflater();
249 inflater.inflate(R.menu.file_actions_menu, menu);
250 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
251 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
252
253 if (mContainerActivity.getStorageManager() != null) {
254 FileMenuFilter mf = new FileMenuFilter(
255 targetFile,
256 mContainerActivity.getStorageManager().getAccount(),
257 mContainerActivity,
258 getSherlockActivity()
259 );
260 mf.filter(menu);
261 }
262
263 /// TODO break this direct dependency on FileDisplayActivity... if possible
264 MenuItem item = menu.findItem(R.id.action_open_file_with);
265 FileFragment frag = ((FileDisplayActivity)getSherlockActivity()).getSecondFragment();
266 if (frag != null && frag instanceof FileDetailFragment &&
267 frag.getFile().getFileId() == targetFile.getFileId()) {
268 item = menu.findItem(R.id.action_see_details);
269 if (item != null) {
270 item.setVisible(false);
271 item.setEnabled(false);
272 }
273 }
274 }
275 }
276
277
278 /**
279 * {@inhericDoc}
280 */
281 @Override
282 public boolean onContextItemSelected (MenuItem item) {
283 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
284 mTargetFile = (OCFile) mAdapter.getItem(info.position);
285 switch (item.getItemId()) {
286 case R.id.action_share_file: {
287 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
288 return true;
289 }
290 case R.id.action_open_file_with: {
291 mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
292 return true;
293 }
294 case R.id.action_unshare_file: {
295 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
296 return true;
297 }
298 case R.id.action_rename_file: {
299 RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile);
300 dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
301 return true;
302 }
303 case R.id.action_remove_file: {
304 RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile);
305 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
306 return true;
307 }
308 case R.id.action_download_file:
309 case R.id.action_sync_file: {
310 mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
311 return true;
312 }
313 case R.id.action_cancel_download:
314 case R.id.action_cancel_upload: {
315 ((FileDisplayActivity)mContainerActivity).cancelTransference(mTargetFile);
316 return true;
317 }
318 case R.id.action_see_details: {
319 mContainerActivity.showDetails(mTargetFile);
320 return true;
321 }
322 case R.id.action_send_file: {
323 // Obtain the file
324 if (!mTargetFile.isDown()) { // Download the file
325 Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
326 ((FileDisplayActivity)mContainerActivity).startDownloadForSending(mTargetFile);
327
328 } else {
329 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
330 }
331 return true;
332 }
333 case R.id.action_move: {
334 Intent action = new Intent(getActivity(), FolderPickerActivity.class);
335
336 // Pass mTargetFile that contains info of selected file/folder
337 action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
338 getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES);
339 return true;
340 }
341 default:
342 return super.onContextItemSelected(item);
343 }
344 }
345
346
347 /**
348 * Use this to query the {@link OCFile} that is currently
349 * being displayed by this fragment
350 * @return The currently viewed OCFile
351 */
352 public OCFile getCurrentFile(){
353 return mFile;
354 }
355
356 /**
357 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
358 */
359 public void listDirectory(boolean onlyOnDevice){
360 listDirectory(null, onlyOnDevice);
361 }
362
363 public void refreshDirectory(){
364 listDirectory(getCurrentFile(), MainApp.getOnlyOnDevice());
365 }
366
367 /**
368 * Lists the given directory on the view. When the input parameter is null,
369 * it will either refresh the last known directory. list the root
370 * if there never was a directory.
371 *
372 * @param directory File to be listed
373 */
374 public void listDirectory(OCFile directory, boolean onlyOnDevice) {
375 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
376 if (storageManager != null) {
377
378 // Check input parameters for null
379 if(directory == null){
380 if(mFile != null){
381 directory = mFile;
382 } else {
383 directory = storageManager.getFileByPath("/");
384 if (directory == null) return; // no files, wait for sync
385 }
386 }
387
388
389 // If that's not a directory -> List its parent
390 if(!directory.isFolder()){
391 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
392 directory = storageManager.getFileById(directory.getParentId());
393 }
394
395 mAdapter.swapDirectory(directory, storageManager, onlyOnDevice);
396 if (mFile == null || !mFile.equals(directory)) {
397 mList.setSelectionFromTop(0, 0);
398 }
399 mFile = directory;
400
401 // Update Footer
402 TextView footerText = (TextView) mFooterView.findViewById(R.id.footerText);
403 Log_OC.d("footer", String.valueOf(System.currentTimeMillis()));
404 footerText.setText(generateFooterText(directory, onlyOnDevice));
405 Log_OC.d("footer", String.valueOf(System.currentTimeMillis()));
406 }
407 }
408
409 private String generateFooterText(OCFile directory, boolean onlyOnDevice) {
410 Integer files = 0;
411 Integer folders = 0;
412
413 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
414 Vector<OCFile> mFiles = storageManager.getFolderContent(mFile, onlyOnDevice);
415
416 for (OCFile ocFile : mFiles) {
417 if (ocFile.isFolder()) {
418 folders++;
419 } else {
420 files++;
421 }
422 }
423
424 String output = "";
425
426 if (files > 0){
427 if (files == 1) {
428 output = output + files.toString() + " " + getResources().getString(R.string.file_list_file);
429 } else {
430 output = output + files.toString() + " " + getResources().getString(R.string.file_list_files);
431 }
432 }
433 if (folders > 0 && files > 0){
434 output = output + ", ";
435 }
436 if (folders == 1) {
437 output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folder);
438 } else if (folders > 1) {
439 output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folders);
440 }
441
442 return output;
443 }
444
445 public void sortByName(boolean descending) {
446 mAdapter.setSortOrder(FileListListAdapter.SORT_NAME, descending);
447 }
448
449 public void sortByDate(boolean descending) {
450 mAdapter.setSortOrder(FileListListAdapter.SORT_DATE, descending);
451 }
452
453 public void sortBySize(boolean descending) {
454 mAdapter.setSortOrder(FileListListAdapter.SORT_SIZE, descending);
455 }
456
457
458
459 }