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