Merge branch 'master' of https://github.com/owncloud/android into material_fab
[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.os.Build;
26 import android.os.Bundle;
27 import android.support.v4.app.Fragment;
28 import android.support.v4.widget.SwipeRefreshLayout;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.AbsListView;
33 import android.widget.AdapterView;
34 import android.widget.AdapterView.OnItemClickListener;
35 import android.widget.GridView;
36 import android.widget.ListAdapter;
37 import android.widget.ListView;
38 import android.widget.TextView;
39 import android.widget.Toast;
40
41 import com.getbase.floatingactionbutton.FloatingActionButton;
42 import com.getbase.floatingactionbutton.FloatingActionsMenu;
43 import com.owncloud.android.R;
44 import com.owncloud.android.lib.common.utils.Log_OC;
45 import com.owncloud.android.ui.ExtendedListView;
46 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
47 import com.owncloud.android.ui.adapter.FileListListAdapter;
48
49 import third_parties.in.srain.cube.GridViewWithHeaderAndFooter;
50
51 /**
52 * TODO extending SherlockListFragment instead of SherlockFragment
53 */
54 public class ExtendedListFragment extends Fragment
55 implements OnItemClickListener, OnEnforceableRefreshListener {
56
57 private static final String TAG = ExtendedListFragment.class.getSimpleName();
58
59 private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION";
60 private static final String KEY_INDEXES = "INDEXES";
61 private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS";
62 private static final String KEY_TOPS = "TOPS";
63 private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL";
64 private static final String KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE";
65
66 private SwipeRefreshLayout mRefreshListLayout;
67 private SwipeRefreshLayout mRefreshGridLayout;
68 private SwipeRefreshLayout mRefreshEmptyLayout;
69 private TextView mEmptyListMessage;
70
71 private FloatingActionsMenu fabMain;
72 private FloatingActionButton fabUpload;
73 private FloatingActionButton fabMkdir;
74 private FloatingActionButton fabUploadFromApp;
75
76 // Save the state of the scroll in browsing
77 private ArrayList<Integer> mIndexes;
78 private ArrayList<Integer> mFirstPositions;
79 private ArrayList<Integer> mTops;
80 private int mHeightCell = 0;
81
82 private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = null;
83
84 protected AbsListView mCurrentListView;
85 private ExtendedListView mListView;
86 private View mListFooterView;
87 private GridViewWithHeaderAndFooter mGridView;
88 private View mGridFooterView;
89
90 private ListAdapter mAdapter;
91
92 protected void setListAdapter(ListAdapter listAdapter) {
93 mAdapter = listAdapter;
94 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
95 mCurrentListView.setAdapter(listAdapter);
96 } else {
97 ((ListView)mCurrentListView).setAdapter(listAdapter);
98 }
99
100 mCurrentListView.invalidate();
101 }
102
103 protected AbsListView getListView() {
104 return mCurrentListView;
105 }
106
107 public FloatingActionButton getFabUpload() {
108 return fabUpload;
109 }
110
111 public FloatingActionButton getFabUploadFromApp() {
112 return fabUploadFromApp;
113 }
114
115 public FloatingActionButton getFabMkdir() {
116 return fabMkdir;
117 }
118
119 public FloatingActionsMenu getFabMain() {
120 return fabMain;
121 }
122
123 protected void switchToGridView() {
124 if ((mCurrentListView == mListView)) {
125
126 mListView.setAdapter(null);
127 mRefreshListLayout.setVisibility(View.GONE);
128
129 if (mAdapter instanceof FileListListAdapter) {
130 ((FileListListAdapter) mAdapter).setGridMode(true);
131 }
132 mGridView.setAdapter(mAdapter);
133 mRefreshGridLayout.setVisibility(View.VISIBLE);
134
135 mCurrentListView = mGridView;
136 }
137 }
138
139 protected void switchToListView() {
140 if (mCurrentListView == mGridView) {
141 mGridView.setAdapter(null);
142 mRefreshGridLayout.setVisibility(View.GONE);
143
144 if (mAdapter instanceof FileListListAdapter) {
145 ((FileListListAdapter) mAdapter).setGridMode(false);
146 }
147 mListView.setAdapter(mAdapter);
148 mRefreshListLayout.setVisibility(View.VISIBLE);
149
150 mCurrentListView = mListView;
151 }
152 }
153
154
155 @Override
156 public View onCreateView(LayoutInflater inflater, ViewGroup container,
157 Bundle savedInstanceState) {
158 Log_OC.d(TAG, "onCreateView");
159
160 View v = inflater.inflate(R.layout.list_fragment, null);
161
162 mListView = (ExtendedListView)(v.findViewById(R.id.list_root));
163 mListView.setOnItemClickListener(this);
164 mListFooterView = inflater.inflate(R.layout.list_footer, null, false);
165
166 mGridView = (GridViewWithHeaderAndFooter) (v.findViewById(R.id.grid_root));
167 mGridView.setNumColumns(GridView.AUTO_FIT);
168 mGridView.setOnItemClickListener(this);
169 mGridFooterView = inflater.inflate(R.layout.list_footer, null, false);
170
171 if (savedInstanceState != null) {
172 int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION);
173 if (mCurrentListView == mListView) {
174 Log_OC.v(TAG, "Setting and centering around list position " + referencePosition);
175 mListView.setAndCenterSelection(referencePosition);
176 } else {
177 Log_OC.v(TAG, "Setting grid position " + referencePosition);
178 mGridView.setSelection(referencePosition);
179 }
180 }
181
182 // Pull-down to refresh layout
183 mRefreshListLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_list);
184 mRefreshGridLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_grid);
185 mRefreshEmptyLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_empty);
186 mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view);
187
188 onCreateSwipeToRefresh(mRefreshListLayout);
189 onCreateSwipeToRefresh(mRefreshGridLayout);
190 onCreateSwipeToRefresh(mRefreshEmptyLayout);
191
192 mListView.setEmptyView(mRefreshEmptyLayout);
193 mGridView.setEmptyView(mRefreshEmptyLayout);
194
195 mCurrentListView = mListView; // list as default
196
197 fabMain = (FloatingActionsMenu) v.findViewById(R.id.fab_main);
198 fabUpload = (FloatingActionButton) v.findViewById(R.id.fab_upload);
199 fabMkdir = (FloatingActionButton) v.findViewById(R.id.fab_mkdir);
200 fabUploadFromApp = (FloatingActionButton) v.findViewById(R.id.fab_upload_from_app);
201
202 return v;
203 }
204
205 /**
206 * {@inheritDoc}
207 */
208 @Override
209 public void onActivityCreated(Bundle savedInstanceState) {
210 super.onActivityCreated(savedInstanceState);
211
212 if (savedInstanceState != null) {
213 mIndexes = savedInstanceState.getIntegerArrayList(KEY_INDEXES);
214 mFirstPositions = savedInstanceState.getIntegerArrayList(KEY_FIRST_POSITIONS);
215 mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
216 mHeightCell = savedInstanceState.getInt(KEY_HEIGHT_CELL);
217 setMessageForEmptyList(savedInstanceState.getString(KEY_EMPTY_LIST_MESSAGE));
218
219 } else {
220 mIndexes = new ArrayList<Integer>();
221 mFirstPositions = new ArrayList<Integer>();
222 mTops = new ArrayList<Integer>();
223 mHeightCell = 0;
224 }
225 }
226
227
228 @Override
229 public void onSaveInstanceState(Bundle savedInstanceState) {
230 super.onSaveInstanceState(savedInstanceState);
231 Log_OC.d(TAG, "onSaveInstanceState()");
232 savedInstanceState.putInt(KEY_SAVED_LIST_POSITION, getReferencePosition());
233 savedInstanceState.putIntegerArrayList(KEY_INDEXES, mIndexes);
234 savedInstanceState.putIntegerArrayList(KEY_FIRST_POSITIONS, mFirstPositions);
235 savedInstanceState.putIntegerArrayList(KEY_TOPS, mTops);
236 savedInstanceState.putInt(KEY_HEIGHT_CELL, mHeightCell);
237 savedInstanceState.putString(KEY_EMPTY_LIST_MESSAGE, getEmptyViewText());
238 }
239
240 /**
241 * Calculates the position of the item that will be used as a reference to
242 * reposition the visible items in the list when the device is turned to
243 * other position.
244 *
245 * The current policy is take as a reference the visible item in the center
246 * of the screen.
247 *
248 * @return The position in the list of the visible item in the center of the
249 * screen.
250 */
251 protected int getReferencePosition() {
252 if (mCurrentListView != null) {
253 return (mCurrentListView.getFirstVisiblePosition() +
254 mCurrentListView.getLastVisiblePosition()) / 2;
255 } else {
256 return 0;
257 }
258 }
259
260
261 /*
262 * Restore index and position
263 */
264 protected void restoreIndexAndTopPosition() {
265 if (mIndexes.size() > 0) {
266 // needs to be checked; not every browse-up had a browse-down before
267
268 int index = mIndexes.remove(mIndexes.size() - 1);
269 final int firstPosition = mFirstPositions.remove(mFirstPositions.size() -1);
270 int top = mTops.remove(mTops.size() - 1);
271
272 Log_OC.v(TAG, "Setting selection to position: " + firstPosition + "; top: "
273 + top + "; index: " + index);
274
275 if (mCurrentListView == mListView) {
276 if (mHeightCell*index <= mListView.getHeight()) {
277 mListView.setSelectionFromTop(firstPosition, top);
278 } else {
279 mListView.setSelectionFromTop(index, 0);
280 }
281
282 } else {
283 if (mHeightCell*index <= mGridView.getHeight()) {
284 mGridView.setSelection(firstPosition);
285 //mGridView.smoothScrollToPosition(firstPosition);
286 } else {
287 mGridView.setSelection(index);
288 //mGridView.smoothScrollToPosition(index);
289 }
290 }
291
292 }
293 }
294
295 /*
296 * Save index and top position
297 */
298 protected void saveIndexAndTopPosition(int index) {
299
300 mIndexes.add(index);
301
302 int firstPosition = mCurrentListView.getFirstVisiblePosition();
303 mFirstPositions.add(firstPosition);
304
305 View view = mCurrentListView.getChildAt(0);
306 int top = (view == null) ? 0 : view.getTop() ;
307
308 mTops.add(top);
309
310 // Save the height of a cell
311 mHeightCell = (view == null || mHeightCell != 0) ? mHeightCell : view.getHeight();
312 }
313
314
315 @Override
316 public void onItemClick (AdapterView<?> parent, View view, int position, long id) {
317 // to be @overriden
318 }
319
320 @Override
321 public void onRefresh() {
322 mRefreshListLayout.setRefreshing(false);
323 mRefreshGridLayout.setRefreshing(false);
324 mRefreshEmptyLayout.setRefreshing(false);
325
326 if (mOnRefreshListener != null) {
327 mOnRefreshListener.onRefresh();
328 }
329 }
330 public void setOnRefreshListener(OnEnforceableRefreshListener listener) {
331 mOnRefreshListener = listener;
332 }
333
334
335 /**
336 * Disables swipe gesture.
337 *
338 * Sets the 'enabled' state of the refresh layouts contained in the fragment.
339 *
340 * When 'false' is set, prevents user gestures but keeps the option to refresh programatically,
341 *
342 * @param enabled Desired state for capturing swipe gesture.
343 */
344 public void setSwipeEnabled(boolean enabled) {
345 mRefreshListLayout.setEnabled(enabled);
346 mRefreshGridLayout.setEnabled(enabled);
347 mRefreshEmptyLayout.setEnabled(enabled);
348 }
349
350 /**
351 * Disables FAB.
352 *
353 * Sets the 'visibility' state of the FAB contained in the fragment.
354 *
355 * When 'false' is set, FAB visibility is set to View.GONE programatically,
356 *
357 * @param enabled Desired visibility for the FAB.
358 */
359 public void setFabEnabled(boolean enabled) {
360 if(enabled) {
361 fabMain.setVisibility(View.VISIBLE);
362 } else {
363 fabMain.setVisibility(View.GONE);
364 }
365 }
366
367 /**
368 * Set message for empty list view
369 */
370 public void setMessageForEmptyList(String message) {
371 if (mEmptyListMessage != null) {
372 mEmptyListMessage.setText(message);
373 }
374 }
375
376 /**
377 * Get the text of EmptyListMessage TextView
378 *
379 * @return String
380 */
381 public String getEmptyViewText() {
382 return (mEmptyListMessage != null) ? mEmptyListMessage.getText().toString() : "";
383 }
384
385 private void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
386 // Colors in animations
387 refreshLayout.setColorSchemeResources(R.color.color_accent, R.color.primary,
388 R.color.primary_dark);
389
390 refreshLayout.setOnRefreshListener(this);
391 }
392
393 @Override
394 public void onRefresh(boolean ignoreETag) {
395 mRefreshListLayout.setRefreshing(false);
396 mRefreshGridLayout.setRefreshing(false);
397 mRefreshEmptyLayout.setRefreshing(false);
398
399 if (mOnRefreshListener != null) {
400 mOnRefreshListener.onRefresh();
401 }
402 }
403
404 protected void setChoiceMode(int choiceMode) {
405 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
406 mListView.setChoiceMode(choiceMode);
407 mGridView.setChoiceMode(choiceMode);
408 } else {
409 ((ListView)mListView).setChoiceMode(choiceMode);
410 }
411 }
412
413 protected void registerForContextMenu() {
414 registerForContextMenu(mListView);
415 registerForContextMenu(mGridView);
416 mListView.setOnCreateContextMenuListener(this);
417 mGridView.setOnCreateContextMenuListener(this);
418 }
419
420 /**
421 * TODO doc
422 * To be called before setAdapter, or GridViewWithHeaderAndFooter will throw an exception
423 *
424 * @param enabled
425 */
426 protected void setFooterEnabled(boolean enabled) {
427 if (enabled) {
428 if (mGridView.getFooterViewCount() == 0) {
429 if (mGridFooterView.getParent() != null ) {
430 ((ViewGroup) mGridFooterView.getParent()).removeView(mGridFooterView);
431 }
432 mGridView.addFooterView(mGridFooterView, null, false);
433 }
434 mGridFooterView.invalidate();
435
436 if (mListView.getFooterViewsCount() == 0) {
437 if (mListFooterView.getParent() != null ) {
438 ((ViewGroup) mListFooterView.getParent()).removeView(mListFooterView);
439 }
440 mListView.addFooterView(mListFooterView, null, false);
441 }
442 mListFooterView.invalidate();
443
444 } else {
445 mGridView.removeFooterView(mGridFooterView);
446 mListView.removeFooterView(mListFooterView);
447 }
448 }
449
450 /**
451 * TODO doc
452 * @param text
453 */
454 protected void setFooterText(String text) {
455 if (text != null && text.length() > 0) {
456 ((TextView)mListFooterView.findViewById(R.id.footerText)).setText(text);
457 ((TextView)mGridFooterView.findViewById(R.id.footerText)).setText(text);
458 setFooterEnabled(true);
459
460 } else {
461 setFooterEnabled(false);
462 }
463 }
464
465 }