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