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