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