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