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