ExtendedListFragment.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * Copyright (C) 2012 Bartek Przybylski
  5. * Copyright (C) 2012-2016 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. package com.owncloud.android.ui.fragment;
  21. import android.os.Bundle;
  22. import android.support.annotation.DrawableRes;
  23. import android.support.annotation.StringRes;
  24. import android.support.v4.app.Fragment;
  25. import android.support.v4.view.MenuItemCompat;
  26. import android.support.v4.widget.SwipeRefreshLayout;
  27. import android.view.GestureDetector;
  28. import android.support.v7.widget.SearchView;
  29. import android.view.LayoutInflater;
  30. import android.view.MotionEvent;
  31. import android.view.ScaleGestureDetector;
  32. import android.view.Menu;
  33. import android.view.MenuInflater;
  34. import android.view.MenuItem;
  35. import android.view.View;
  36. import android.view.ViewGroup;
  37. import android.widget.AbsListView;
  38. import android.widget.AdapterView;
  39. import android.widget.AdapterView.OnItemClickListener;
  40. import android.widget.GridView;
  41. import android.widget.ImageView;
  42. import android.widget.LinearLayout;
  43. import android.widget.ProgressBar;
  44. import android.widget.TextView;
  45. import com.getbase.floatingactionbutton.FloatingActionButton;
  46. import com.getbase.floatingactionbutton.FloatingActionsMenu;
  47. import com.owncloud.android.MainApp;
  48. import com.owncloud.android.R;
  49. import com.owncloud.android.lib.common.utils.Log_OC;
  50. import com.owncloud.android.ui.ExtendedListView;
  51. import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
  52. import com.owncloud.android.ui.adapter.FilterableListAdapter;
  53. import java.util.ArrayList;
  54. import third_parties.in.srain.cube.GridViewWithHeaderAndFooter;
  55. public class ExtendedListFragment extends Fragment
  56. implements OnItemClickListener, OnEnforceableRefreshListener, SearchView.OnQueryTextListener {
  57. protected static final String TAG = ExtendedListFragment.class.getSimpleName();
  58. protected 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. private static final String KEY_IS_GRID_VISIBLE = "IS_GRID_VISIBLE";
  65. private ScaleGestureDetector SGD = null;
  66. protected SwipeRefreshLayout mRefreshListLayout;
  67. private SwipeRefreshLayout mRefreshGridLayout;
  68. protected SwipeRefreshLayout mRefreshEmptyLayout;
  69. protected LinearLayout mEmptyListContainer;
  70. protected TextView mEmptyListMessage;
  71. protected TextView mEmptyListHeadline;
  72. protected ImageView mEmptyListIcon;
  73. protected ProgressBar mEmptyListProgress;
  74. private FloatingActionsMenu mFabMain;
  75. private FloatingActionButton mFabUpload;
  76. private FloatingActionButton mFabMkdir;
  77. private FloatingActionButton mFabUploadFromApp;
  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. private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = null;
  84. protected AbsListView mCurrentListView;
  85. private ExtendedListView mListView;
  86. private View mListFooterView;
  87. private GridViewWithHeaderAndFooter mGridView;
  88. private View mGridFooterView;
  89. private FilterableListAdapter mAdapter;
  90. p private float scale = -1f;
  91. protected void setListAdapter(FilterableListAdapter listAdapter) {
  92. mAdapter = listAdapter;
  93. mCurrentListView.setAdapter(listAdapter);
  94. mCurrentListView.invalidateViews();
  95. }
  96. protected AbsListView getListView() {
  97. return mCurrentListView;
  98. }
  99. public FloatingActionButton getFabUpload() {
  100. return mFabUpload;
  101. }
  102. public FloatingActionButton getFabUploadFromApp() {
  103. return mFabUploadFromApp;
  104. }
  105. public FloatingActionButton getFabMkdir() {
  106. return mFabMkdir;
  107. }
  108. public FloatingActionsMenu getFabMain() {
  109. return mFabMain;
  110. }
  111. public void switchToGridView() {
  112. if (!isGridEnabled()) {
  113. mListView.setAdapter(null);
  114. mRefreshListLayout.setVisibility(View.GONE);
  115. mRefreshGridLayout.setVisibility(View.VISIBLE);
  116. mCurrentListView = mGridView;
  117. setListAdapter(mAdapter);
  118. }
  119. }
  120. public void switchToListView() {
  121. if (isGridEnabled()) {
  122. mGridView.setAdapter(null);
  123. mRefreshGridLayout.setVisibility(View.GONE);
  124. mRefreshListLayout.setVisibility(View.VISIBLE);
  125. mCurrentListView = mListView;
  126. setListAdapter(mAdapter);
  127. }
  128. }
  129. public boolean isGridEnabled(){
  130. return (mCurrentListView != null && mCurrentListView.equals(mGridView));
  131. }
  132. @Override
  133. public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
  134. final MenuItem item = menu.findItem(R.id.action_search);
  135. final SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
  136. searchView.setOnQueryTextListener(this);
  137. }
  138. public boolean onQueryTextChange(String query) {
  139. mAdapter.filter(query);
  140. return true;
  141. }
  142. @Override
  143. public boolean onQueryTextSubmit(String query) {
  144. mAdapter.filter(query);
  145. return true;
  146. }
  147. @Override
  148. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  149. Bundle savedInstanceState) {
  150. Log_OC.d(TAG, "onCreateView");
  151. View v = inflater.inflate(R.layout.list_fragment, null);
  152. setupEmptyList(v);
  153. mListView = (ExtendedListView)(v.findViewById(R.id.list_root));
  154. mListView.setOnItemClickListener(this);
  155. mListFooterView = inflater.inflate(R.layout.list_footer, null, false);
  156. mGridView = (GridViewWithHeaderAndFooter) (v.findViewById(R.id.grid_root));
  157. mGridView.setNumColumns(GridView.AUTO_FIT);
  158. mGridView.setOnItemClickListener(this);
  159. mGridFooterView = inflater.inflate(R.layout.list_footer, null, false);
  160. SGD = new ScaleGestureDetector(MainApp.getAppContext(),new ScaleListener());
  161. // gestureDetector = new GestureDetector(MainApp.getAppContext(), new SingleTapConfirm());
  162. mGridView.setOnTouchListener(new View.OnTouchListener() {
  163. @Override
  164. public boolean onTouch(View view, MotionEvent motionEvent) {
  165. // if (SGD.onTouchEvent(motionEvent)) {
  166. // return false;
  167. // }
  168. // return false;
  169. SGD.onTouchEvent(motionEvent);
  170. return false;
  171. }
  172. });
  173. if (savedInstanceState != null) {
  174. int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION);
  175. if (mCurrentListView == mListView) {
  176. Log_OC.v(TAG, "Setting and centering around list position " + referencePosition);
  177. mListView.setAndCenterSelection(referencePosition);
  178. } else {
  179. Log_OC.v(TAG, "Setting grid position " + referencePosition);
  180. mGridView.setSelection(referencePosition);
  181. }
  182. }
  183. // Pull-down to refresh layout
  184. mRefreshListLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_list);
  185. mRefreshGridLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_grid);
  186. mRefreshEmptyLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_containing_empty);
  187. onCreateSwipeToRefresh(mRefreshListLayout);
  188. onCreateSwipeToRefresh(mRefreshGridLayout);
  189. onCreateSwipeToRefresh(mRefreshEmptyLayout);
  190. mListView.setEmptyView(mRefreshEmptyLayout);
  191. mGridView.setEmptyView(mRefreshEmptyLayout);
  192. mFabMain = (FloatingActionsMenu) v.findViewById(R.id.fab_main);
  193. mFabUpload = (FloatingActionButton) v.findViewById(R.id.fab_upload);
  194. mFabMkdir = (FloatingActionButton) v.findViewById(R.id.fab_mkdir);
  195. mFabUploadFromApp = (FloatingActionButton) v.findViewById(R.id.fab_upload_from_app);
  196. mCurrentListView = mListView; // list by default
  197. if (savedInstanceState != null) {
  198. if (savedInstanceState.getBoolean(KEY_IS_GRID_VISIBLE, false)) {
  199. switchToGridView();
  200. }
  201. int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION);
  202. if (isGridEnabled()) {
  203. Log_OC.v(TAG, "Setting grid position " + referencePosition);
  204. mGridView.setSelection(referencePosition);
  205. } else {
  206. Log_OC.v(TAG, "Setting and centering around list position " + referencePosition);
  207. mListView.setAndCenterSelection(referencePosition);
  208. }
  209. }
  210. return v;
  211. }
  212. private class SingleTapConfirm extends GestureDetector.SimpleOnGestureListener {
  213. @Override
  214. public boolean onSingleTapUp(MotionEvent e) {
  215. return true;
  216. }
  217. }
  218. private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
  219. @Override
  220. public boolean onScale(ScaleGestureDetector detector) {
  221. if (scale == -1f){
  222. mGridView.setNumColumns(GridView.AUTO_FIT);
  223. scale = mGridView.getNumColumns();
  224. }
  225. scale *= 1-(detector.getScaleFactor()- 1);
  226. scale = Math.max(2.0f, Math.min(scale, 10.0f));
  227. Integer scaleInt = Math.round(scale);
  228. mGridView.setNumColumns(scaleInt);
  229. mGridView.invalidateViews();
  230. return true;
  231. }
  232. }
  233. protected void setupEmptyList(View view) {
  234. mEmptyListContainer = (LinearLayout) view.findViewById(R.id.empty_list_view);
  235. mEmptyListMessage = (TextView) view.findViewById(R.id.empty_list_view_text);
  236. mEmptyListHeadline = (TextView) view.findViewById(R.id.empty_list_view_headline);
  237. mEmptyListIcon = (ImageView) view.findViewById(R.id.empty_list_icon);
  238. mEmptyListProgress = (ProgressBar) view.findViewById(R.id.empty_list_progress);
  239. }
  240. /**
  241. * {@inheritDoc}
  242. */
  243. @Override
  244. public void onActivityCreated(Bundle savedInstanceState) {
  245. super.onActivityCreated(savedInstanceState);
  246. if (savedInstanceState != null) {
  247. mIndexes = savedInstanceState.getIntegerArrayList(KEY_INDEXES);
  248. mFirstPositions = savedInstanceState.getIntegerArrayList(KEY_FIRST_POSITIONS);
  249. mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
  250. mHeightCell = savedInstanceState.getInt(KEY_HEIGHT_CELL);
  251. setMessageForEmptyList(savedInstanceState.getString(KEY_EMPTY_LIST_MESSAGE));
  252. } else {
  253. mIndexes = new ArrayList<>();
  254. mFirstPositions = new ArrayList<>();
  255. mTops = new ArrayList<>();
  256. mHeightCell = 0;
  257. }
  258. }
  259. @Override
  260. public void onSaveInstanceState(Bundle savedInstanceState) {
  261. super.onSaveInstanceState(savedInstanceState);
  262. Log_OC.d(TAG, "onSaveInstanceState()");
  263. savedInstanceState.putBoolean(KEY_IS_GRID_VISIBLE, isGridEnabled());
  264. savedInstanceState.putInt(KEY_SAVED_LIST_POSITION, getReferencePosition());
  265. savedInstanceState.putIntegerArrayList(KEY_INDEXES, mIndexes);
  266. savedInstanceState.putIntegerArrayList(KEY_FIRST_POSITIONS, mFirstPositions);
  267. savedInstanceState.putIntegerArrayList(KEY_TOPS, mTops);
  268. savedInstanceState.putInt(KEY_HEIGHT_CELL, mHeightCell);
  269. savedInstanceState.putString(KEY_EMPTY_LIST_MESSAGE, getEmptyViewText());
  270. }
  271. /**
  272. * Calculates the position of the item that will be used as a reference to
  273. * reposition the visible items in the list when the device is turned to
  274. * other position.
  275. *
  276. * The current policy is take as a reference the visible item in the center
  277. * of the screen.
  278. *
  279. * @return The position in the list of the visible item in the center of the
  280. * screen.
  281. */
  282. protected int getReferencePosition() {
  283. if (mCurrentListView != null) {
  284. return (mCurrentListView.getFirstVisiblePosition() +
  285. mCurrentListView.getLastVisiblePosition()) / 2;
  286. } else {
  287. return 0;
  288. }
  289. }
  290. /*
  291. * Restore index and position
  292. */
  293. protected void restoreIndexAndTopPosition() {
  294. if (mIndexes.size() > 0) {
  295. // needs to be checked; not every browse-up had a browse-down before
  296. int index = mIndexes.remove(mIndexes.size() - 1);
  297. final int firstPosition = mFirstPositions.remove(mFirstPositions.size() -1);
  298. int top = mTops.remove(mTops.size() - 1);
  299. Log_OC.v(TAG, "Setting selection to position: " + firstPosition + "; top: "
  300. + top + "; index: " + index);
  301. if (mCurrentListView!= null && mCurrentListView.equals(mListView)) {
  302. if (mHeightCell*index <= mListView.getHeight()) {
  303. mListView.setSelectionFromTop(firstPosition, top);
  304. } else {
  305. mListView.setSelectionFromTop(index, 0);
  306. }
  307. } else {
  308. if (mHeightCell*index <= mGridView.getHeight()) {
  309. mGridView.setSelection(firstPosition);
  310. //mGridView.smoothScrollToPosition(firstPosition);
  311. } else {
  312. mGridView.setSelection(index);
  313. //mGridView.smoothScrollToPosition(index);
  314. }
  315. }
  316. }
  317. }
  318. /*
  319. * Save index and top position
  320. */
  321. protected void saveIndexAndTopPosition(int index) {
  322. mIndexes.add(index);
  323. int firstPosition = mCurrentListView.getFirstVisiblePosition();
  324. mFirstPositions.add(firstPosition);
  325. View view = mCurrentListView.getChildAt(0);
  326. int top = (view == null) ? 0 : view.getTop() ;
  327. mTops.add(top);
  328. // Save the height of a cell
  329. mHeightCell = (view == null || mHeightCell != 0) ? mHeightCell : view.getHeight();
  330. }
  331. @Override
  332. public void onItemClick (AdapterView<?> parent, View view, int position, long id) {
  333. // to be @overriden
  334. }
  335. @Override
  336. public void onRefresh() {
  337. mRefreshListLayout.setRefreshing(false);
  338. mRefreshGridLayout.setRefreshing(false);
  339. mRefreshEmptyLayout.setRefreshing(false);
  340. if (mOnRefreshListener != null) {
  341. mOnRefreshListener.onRefresh();
  342. }
  343. }
  344. public void setOnRefreshListener(OnEnforceableRefreshListener listener) {
  345. mOnRefreshListener = listener;
  346. }
  347. /**
  348. * Disables swipe gesture.
  349. *
  350. * Sets the 'enabled' state of the refresh layouts contained in the fragment.
  351. *
  352. * When 'false' is set, prevents user gestures but keeps the option to refresh programatically,
  353. *
  354. * @param enabled Desired state for capturing swipe gesture.
  355. */
  356. public void setSwipeEnabled(boolean enabled) {
  357. mRefreshListLayout.setEnabled(enabled);
  358. mRefreshGridLayout.setEnabled(enabled);
  359. mRefreshEmptyLayout.setEnabled(enabled);
  360. }
  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 programmatically,
  365. *
  366. * @param enabled Desired visibility for the FAB.
  367. */
  368. public void setFabEnabled(boolean enabled) {
  369. if(enabled) {
  370. mFabMain.setVisibility(View.VISIBLE);
  371. } else {
  372. mFabMain.setVisibility(View.GONE);
  373. }
  374. }
  375. /**
  376. * Set message for empty list view.
  377. */
  378. public void setMessageForEmptyList(String message) {
  379. if (mEmptyListContainer != null && mEmptyListMessage != null) {
  380. mEmptyListMessage.setText(message);
  381. }
  382. }
  383. /**
  384. * displays an empty list information with a headline, a message and an icon.
  385. *
  386. * @param headline the headline
  387. * @param message the message
  388. * @param icon the icon to be shown
  389. */
  390. public void setMessageForEmptyList(@StringRes int headline, @StringRes int message, @DrawableRes int icon) {
  391. if (mEmptyListContainer != null && mEmptyListMessage != null) {
  392. mEmptyListHeadline.setText(headline);
  393. mEmptyListMessage.setText(message);
  394. mEmptyListIcon.setImageResource(icon);
  395. mEmptyListIcon.setVisibility(View.VISIBLE);
  396. mEmptyListProgress.setVisibility(View.GONE);
  397. }
  398. }
  399. /**
  400. * Set message for empty list view.
  401. */
  402. public void setEmptyListMessage() {
  403. setMessageForEmptyList(
  404. R.string.file_list_empty_headline,
  405. R.string.file_list_empty,
  406. R.drawable.ic_list_empty_folder
  407. );
  408. }
  409. /**
  410. * Set message for empty list view.
  411. */
  412. public void setEmptyListLoadingMessage() {
  413. if (mEmptyListContainer != null && mEmptyListMessage != null) {
  414. mEmptyListHeadline.setText(R.string.file_list_loading);
  415. mEmptyListMessage.setText("");
  416. mEmptyListIcon.setVisibility(View.GONE);
  417. mEmptyListProgress.setVisibility(View.VISIBLE);
  418. }
  419. }
  420. /**
  421. * Get the text of EmptyListMessage TextView.
  422. *
  423. * @return String empty text view text-value
  424. */
  425. public String getEmptyViewText() {
  426. return (mEmptyListContainer != null && mEmptyListMessage != null) ? mEmptyListMessage.getText().toString() : "";
  427. }
  428. protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
  429. // Colors in animations
  430. refreshLayout.setColorSchemeResources(R.color.color_accent, R.color.primary, R.color.primary_dark);
  431. refreshLayout.setOnRefreshListener(this);
  432. }
  433. @Override
  434. public void onRefresh(boolean ignoreETag) {
  435. mRefreshListLayout.setRefreshing(false);
  436. mRefreshGridLayout.setRefreshing(false);
  437. mRefreshEmptyLayout.setRefreshing(false);
  438. if (mOnRefreshListener != null) {
  439. mOnRefreshListener.onRefresh();
  440. }
  441. }
  442. protected void setChoiceMode(int choiceMode) {
  443. mListView.setChoiceMode(choiceMode);
  444. mGridView.setChoiceMode(choiceMode);
  445. }
  446. protected void setMultiChoiceModeListener(AbsListView.MultiChoiceModeListener listener) {
  447. mListView.setMultiChoiceModeListener(listener);
  448. mGridView.setMultiChoiceModeListener(listener);
  449. }
  450. /**
  451. * TODO doc
  452. * To be called before setAdapter, or GridViewWithHeaderAndFooter will throw an exception
  453. *
  454. * @param enabled flag if footer should be shown/calculated
  455. */
  456. protected void setFooterEnabled(boolean enabled) {
  457. if (enabled) {
  458. if (mGridView.getFooterViewCount() == 0 && mGridView.isCorrectAdapter()) {
  459. if (mGridFooterView.getParent() != null ) {
  460. ((ViewGroup) mGridFooterView.getParent()).removeView(mGridFooterView);
  461. }
  462. mGridView.addFooterView(mGridFooterView, null, false);
  463. }
  464. mGridFooterView.invalidate();
  465. if (mListView.getFooterViewsCount() == 0) {
  466. if (mListFooterView.getParent() != null ) {
  467. ((ViewGroup) mListFooterView.getParent()).removeView(mListFooterView);
  468. }
  469. mListView.addFooterView(mListFooterView, null, false);
  470. }
  471. mListFooterView.invalidate();
  472. } else {
  473. mGridView.removeFooterView(mGridFooterView);
  474. mListView.removeFooterView(mListFooterView);
  475. }
  476. }
  477. /**
  478. * set the list/grid footer text.
  479. *
  480. * @param text the footer text
  481. */
  482. protected void setFooterText(String text) {
  483. if (text != null && text.length() > 0) {
  484. ((TextView)mListFooterView.findViewById(R.id.footerText)).setText(text);
  485. ((TextView)mGridFooterView.findViewById(R.id.footerText)).setText(text);
  486. setFooterEnabled(true);
  487. } else {
  488. setFooterEnabled(false);
  489. }
  490. }
  491. }