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