Merge remote-tracking branch 'remotes/upstream/master' into beta
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / ExtendedListFragment.java
1 /**
2 * ownCloud Android client application
3 *
4 * Copyright (C) 2012 Bartek Przybylski
5 * Copyright (C) 2012-2015 ownCloud Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 package com.owncloud.android.ui.fragment;
22
23 import java.util.ArrayList;
24
25 import android.app.Activity;
26 import android.os.Build;
27 import android.os.Bundle;
28 import android.support.v4.app.Fragment;
29 import android.support.v4.widget.SwipeRefreshLayout;
30 import android.view.ActionMode;
31 import android.view.LayoutInflater;
32 import android.view.Menu;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.AbsListView;
37 import android.widget.AdapterView;
38 import android.widget.AdapterView.OnItemClickListener;
39 import android.widget.GridView;
40 import android.widget.ListAdapter;
41 import android.widget.ListView;
42 import android.widget.TextView;
43 import android.widget.Toast;
44
45 import com.getbase.floatingactionbutton.FloatingActionButton;
46 import com.getbase.floatingactionbutton.FloatingActionsMenu;
47 import com.owncloud.android.R;
48 import com.owncloud.android.lib.common.utils.Log_OC;
49 import com.owncloud.android.ui.ExtendedListView;
50 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
51 import com.owncloud.android.ui.adapter.FileListListAdapter;
52 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
53
54 import third_parties.in.srain.cube.GridViewWithHeaderAndFooter;
55
56 /**
57 * TODO extending SherlockListFragment instead of SherlockFragment
58 */
59 public class ExtendedListFragment extends Fragment
60 implements OnItemClickListener, OnEnforceableRefreshListener {
61
62 private static final String TAG = ExtendedListFragment.class.getSimpleName();
63
64 private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION";
65 private static final String KEY_INDEXES = "INDEXES";
66 private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS";
67 private static final String KEY_TOPS = "TOPS";
68 private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL";
69 private static final String KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE";
70
71 private SwipeRefreshLayout mRefreshListLayout;
72 private SwipeRefreshLayout mRefreshGridLayout;
73 private SwipeRefreshLayout mRefreshEmptyLayout;
74 private TextView mEmptyListMessage;
75
76 private FloatingActionsMenu fabMain;
77 private FloatingActionButton fabUpload;
78 private FloatingActionButton fabMkdir;
79 private FloatingActionButton fabUploadFromApp;
80
81 // Save the state of the scroll in browsing
82 private ArrayList<Integer> mIndexes;
83 private ArrayList<Integer> mFirstPositions;
84 private ArrayList<Integer> mTops;
85 private int mHeightCell = 0;
86
87 private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = null;
88
89 protected AbsListView mCurrentListView;
90 private ExtendedListView mListView;
91 private View mListFooterView;
92 private GridViewWithHeaderAndFooter mGridView;
93 private View mGridFooterView;
94
95 private ListAdapter mAdapter;
96
97 protected void setListAdapter(ListAdapter listAdapter) {
98 mAdapter = listAdapter;
99 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
100 mCurrentListView.setAdapter(listAdapter);
101 } else {
102 ((ListView)mCurrentListView).setAdapter(listAdapter);
103 }
104
105 mCurrentListView.invalidate();
106 }
107
108 protected AbsListView getListView() {
109 return mCurrentListView;
110 }
111
112 public FloatingActionButton getFabUpload() {
113 return fabUpload;
114 }
115
116 public FloatingActionButton getFabUploadFromApp() {
117 return fabUploadFromApp;
118 }
119
120 public FloatingActionButton getFabMkdir() {
121 return fabMkdir;
122 }
123
124 public FloatingActionsMenu getFabMain() {
125 return fabMain;
126 }
127
128 public void switchToGridView() {
129 if ((mCurrentListView == mListView)) {
130
131 mListView.setAdapter(null);
132 mRefreshListLayout.setVisibility(View.GONE);
133
134 if (mAdapter instanceof FileListListAdapter) {
135 ((FileListListAdapter) mAdapter).setGridMode(true);
136 }
137 mGridView.setAdapter(mAdapter);
138 mRefreshGridLayout.setVisibility(View.VISIBLE);
139
140 mCurrentListView = mGridView;
141 }
142 }
143
144 public void switchToListView() {
145 if (mCurrentListView == mGridView) {
146 mGridView.setAdapter(null);
147 mRefreshGridLayout.setVisibility(View.GONE);
148
149 if (mAdapter instanceof FileListListAdapter) {
150 ((FileListListAdapter) mAdapter).setGridMode(false);
151 }
152 mListView.setAdapter(mAdapter);
153 mRefreshListLayout.setVisibility(View.VISIBLE);
154
155 mCurrentListView = mListView;
156 }
157 }
158
159 public boolean isGridView(){
160 if (mAdapter instanceof FileListListAdapter) {
161 return ((FileListListAdapter) mAdapter).isGridMode();
162 }
163 return false;
164 }
165
166
167 @Override
168 public View onCreateView(LayoutInflater inflater, ViewGroup container,
169 Bundle savedInstanceState) {
170 Log_OC.d(TAG, "onCreateView");
171
172 // TODO Tobi remove
173 // AbsListView.MultiChoiceModeListener listener = new AbsListView.MultiChoiceModeListener() {
174 // @Override
175 // public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
176 // // Capture total checked items
177 // final int checkedCount = mListView.getCheckedItemCount();
178 // // Set the CAB title according to total checked items
179 // mode.setTitle(checkedCount + " Selected");
180 // // Calls toggleSelection method from ListViewAdapter Class
181 // // mAdapter.toggleSelection(position);
182 //
183 // if (checked){
184 // mAdapter.setNewSelection(position,checked);
185 // } else {
186 // mAdapter.removeSelection(position);
187 // }
188 // }
189 //
190 // @Override
191 // public boolean onCreateActionMode(ActionMode mode, Menu menu) {
192 // mode.getMenuInflater().inflate(R.menu.context, menu);
193 // return true;
194 // }
195 //
196 // @Override
197 // public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
198 // return false;
199 // }
200 //
201 // @Override
202 // public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
203 // return false;
204 // }
205 //
206 // @Override
207 // public void onDestroyActionMode(ActionMode mode) {
208 // // mAdapter.removeSelection();
209 // }
210 // };
211
212 View v = inflater.inflate(R.layout.list_fragment, null);
213
214 mListView = (ExtendedListView)(v.findViewById(R.id.list_root));
215 mListView.setOnItemClickListener(this);
216 mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
217 // mListView.setMultiChoiceModeListener(listener);
218 mListFooterView = inflater.inflate(R.layout.list_footer, null, false);
219
220 mGridView = (GridViewWithHeaderAndFooter) (v.findViewById(R.id.grid_root));
221 mGridView.setNumColumns(GridView.AUTO_FIT);
222 mGridView.setOnItemClickListener(this);
223 mGridView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
224
225 // mGridView.setMultiChoiceModeListener(listener);
226
227 mGridFooterView = inflater.inflate(R.layout.list_footer, null, false);
228
229 if (savedInstanceState != null) {
230 int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION);
231 if (mCurrentListView == mListView) {
232 Log_OC.v(TAG, "Setting and centering around list position " + referencePosition);
233 mListView.setAndCenterSelection(referencePosition);
234 } else {
235 Log_OC.v(TAG, "Setting grid position " + referencePosition);
236 mGridView.setSelection(referencePosition);
237 }
238 }
239
240 // Pull-down to refresh layout
241 mRefreshListLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_list);
242 mRefreshGridLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_grid);
243 mRefreshEmptyLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_empty);
244 mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view);
245
246 onCreateSwipeToRefresh(mRefreshListLayout);
247 onCreateSwipeToRefresh(mRefreshGridLayout);
248 onCreateSwipeToRefresh(mRefreshEmptyLayout);
249
250 mListView.setEmptyView(mRefreshEmptyLayout);
251 mGridView.setEmptyView(mRefreshEmptyLayout);
252
253 mCurrentListView = mListView; // list as default
254
255 fabMain = (FloatingActionsMenu) v.findViewById(R.id.fab_main);
256 fabUpload = (FloatingActionButton) v.findViewById(R.id.fab_upload);
257 fabMkdir = (FloatingActionButton) v.findViewById(R.id.fab_mkdir);
258 fabUploadFromApp = (FloatingActionButton) v.findViewById(R.id.fab_upload_from_app);
259
260 return v;
261 }
262
263 /**
264 * {@inheritDoc}
265 */
266 @Override
267 public void onActivityCreated(Bundle savedInstanceState) {
268 super.onActivityCreated(savedInstanceState);
269
270 if (savedInstanceState != null) {
271 mIndexes = savedInstanceState.getIntegerArrayList(KEY_INDEXES);
272 mFirstPositions = savedInstanceState.getIntegerArrayList(KEY_FIRST_POSITIONS);
273 mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
274 mHeightCell = savedInstanceState.getInt(KEY_HEIGHT_CELL);
275 setMessageForEmptyList(savedInstanceState.getString(KEY_EMPTY_LIST_MESSAGE));
276
277 } else {
278 mIndexes = new ArrayList<Integer>();
279 mFirstPositions = new ArrayList<Integer>();
280 mTops = new ArrayList<Integer>();
281 mHeightCell = 0;
282 }
283 }
284
285
286 @Override
287 public void onSaveInstanceState(Bundle savedInstanceState) {
288 super.onSaveInstanceState(savedInstanceState);
289 Log_OC.d(TAG, "onSaveInstanceState()");
290 savedInstanceState.putInt(KEY_SAVED_LIST_POSITION, getReferencePosition());
291 savedInstanceState.putIntegerArrayList(KEY_INDEXES, mIndexes);
292 savedInstanceState.putIntegerArrayList(KEY_FIRST_POSITIONS, mFirstPositions);
293 savedInstanceState.putIntegerArrayList(KEY_TOPS, mTops);
294 savedInstanceState.putInt(KEY_HEIGHT_CELL, mHeightCell);
295 savedInstanceState.putString(KEY_EMPTY_LIST_MESSAGE, getEmptyViewText());
296 }
297
298 /**
299 * Calculates the position of the item that will be used as a reference to
300 * reposition the visible items in the list when the device is turned to
301 * other position.
302 *
303 * The current policy is take as a reference the visible item in the center
304 * of the screen.
305 *
306 * @return The position in the list of the visible item in the center of the
307 * screen.
308 */
309 protected int getReferencePosition() {
310 if (mCurrentListView != null) {
311 return (mCurrentListView.getFirstVisiblePosition() +
312 mCurrentListView.getLastVisiblePosition()) / 2;
313 } else {
314 return 0;
315 }
316 }
317
318
319 /*
320 * Restore index and position
321 */
322 protected void restoreIndexAndTopPosition() {
323 if (mIndexes.size() > 0) {
324 // needs to be checked; not every browse-up had a browse-down before
325
326 int index = mIndexes.remove(mIndexes.size() - 1);
327 final int firstPosition = mFirstPositions.remove(mFirstPositions.size() -1);
328 int top = mTops.remove(mTops.size() - 1);
329
330 Log_OC.v(TAG, "Setting selection to position: " + firstPosition + "; top: "
331 + top + "; index: " + index);
332
333 if (mCurrentListView == mListView) {
334 if (mHeightCell*index <= mListView.getHeight()) {
335 mListView.setSelectionFromTop(firstPosition, top);
336 } else {
337 mListView.setSelectionFromTop(index, 0);
338 }
339
340 } else {
341 if (mHeightCell*index <= mGridView.getHeight()) {
342 mGridView.setSelection(firstPosition);
343 //mGridView.smoothScrollToPosition(firstPosition);
344 } else {
345 mGridView.setSelection(index);
346 //mGridView.smoothScrollToPosition(index);
347 }
348 }
349
350 }
351 }
352
353 /*
354 * Save index and top position
355 */
356 protected void saveIndexAndTopPosition(int index) {
357
358 mIndexes.add(index);
359
360 int firstPosition = mCurrentListView.getFirstVisiblePosition();
361 mFirstPositions.add(firstPosition);
362
363 View view = mCurrentListView.getChildAt(0);
364 int top = (view == null) ? 0 : view.getTop() ;
365
366 mTops.add(top);
367
368 // Save the height of a cell
369 mHeightCell = (view == null || mHeightCell != 0) ? mHeightCell : view.getHeight();
370 }
371
372
373 @Override
374 public void onItemClick (AdapterView<?> parent, View view, int position, long id) {
375 // to be @overriden
376 }
377
378 @Override
379 public void onRefresh() {
380 mRefreshListLayout.setRefreshing(false);
381 mRefreshGridLayout.setRefreshing(false);
382 mRefreshEmptyLayout.setRefreshing(false);
383
384 if (mOnRefreshListener != null) {
385 mOnRefreshListener.onRefresh();
386 }
387 }
388 public void setOnRefreshListener(OnEnforceableRefreshListener listener) {
389 mOnRefreshListener = listener;
390 }
391
392
393 /**
394 * Disables swipe gesture.
395 *
396 * Sets the 'enabled' state of the refresh layouts contained in the fragment.
397 *
398 * When 'false' is set, prevents user gestures but keeps the option to refresh programatically,
399 *
400 * @param enabled Desired state for capturing swipe gesture.
401 */
402 public void setSwipeEnabled(boolean enabled) {
403 mRefreshListLayout.setEnabled(enabled);
404 mRefreshGridLayout.setEnabled(enabled);
405 mRefreshEmptyLayout.setEnabled(enabled);
406 }
407
408 /**
409 * Disables FAB.
410 *
411 * Sets the 'visibility' state of the FAB contained in the fragment.
412 *
413 * When 'false' is set, FAB visibility is set to View.GONE programatically,
414 *
415 * @param enabled Desired visibility for the FAB.
416 */
417 public void setFabEnabled(boolean enabled) {
418 if(enabled) {
419 fabMain.setVisibility(View.VISIBLE);
420 } else {
421 fabMain.setVisibility(View.GONE);
422 }
423 }
424
425 /**
426 * Set message for empty list view
427 */
428 public void setMessageForEmptyList(String message) {
429 if (mEmptyListMessage != null) {
430 mEmptyListMessage.setText(message);
431 }
432 }
433
434 /**
435 * Get the text of EmptyListMessage TextView
436 *
437 * @return String
438 */
439 public String getEmptyViewText() {
440 return (mEmptyListMessage != null) ? mEmptyListMessage.getText().toString() : "";
441 }
442
443 private void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
444 // Colors in animations
445 refreshLayout.setColorSchemeResources(R.color.color_accent, R.color.primary,
446 R.color.primary_dark);
447
448 refreshLayout.setOnRefreshListener(this);
449 }
450
451 @Override
452 public void onRefresh(boolean ignoreETag) {
453 mRefreshListLayout.setRefreshing(false);
454 mRefreshGridLayout.setRefreshing(false);
455 mRefreshEmptyLayout.setRefreshing(false);
456
457 if (mOnRefreshListener != null) {
458 mOnRefreshListener.onRefresh();
459 }
460 }
461
462 protected void setChoiceMode(int choiceMode) {
463 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
464 mListView.setChoiceMode(choiceMode);
465 mGridView.setChoiceMode(choiceMode);
466 } else {
467 ((ListView)mListView).setChoiceMode(choiceMode);
468 }
469 }
470
471 protected void registerForContextMenu() {
472 registerForContextMenu(mListView);
473 registerForContextMenu(mGridView);
474 mListView.setOnCreateContextMenuListener(this);
475 mGridView.setOnCreateContextMenuListener(this);
476 }
477
478 /**
479 * TODO doc
480 * To be called before setAdapter, or GridViewWithHeaderAndFooter will throw an exception
481 *
482 * @param enabled
483 */
484 protected void setFooterEnabled(boolean enabled) {
485 if (enabled) {
486 if (mGridView.getFooterViewCount() == 0) {
487 if (mGridFooterView.getParent() != null ) {
488 ((ViewGroup) mGridFooterView.getParent()).removeView(mGridFooterView);
489 }
490 mGridView.addFooterView(mGridFooterView, null, false);
491 }
492 mGridFooterView.invalidate();
493
494 if (mListView.getFooterViewsCount() == 0) {
495 if (mListFooterView.getParent() != null ) {
496 ((ViewGroup) mListFooterView.getParent()).removeView(mListFooterView);
497 }
498 mListView.addFooterView(mListFooterView, null, false);
499 }
500 mListFooterView.invalidate();
501
502 } else {
503 mGridView.removeFooterView(mGridFooterView);
504 mListView.removeFooterView(mListFooterView);
505 }
506 }
507
508 /**
509 * TODO doc
510 * @param text
511 */
512 protected void setFooterText(String text) {
513 if (text != null && text.length() > 0) {
514 ((TextView)mListFooterView.findViewById(R.id.footerText)).setText(text);
515 ((TextView)mGridFooterView.findViewById(R.id.footerText)).setText(text);
516 setFooterEnabled(true);
517
518 } else {
519 setFooterEnabled(false);
520 }
521 }
522
523 }