Application Android et IOS pour l'amicale des élèves
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WebSectionList.js 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // @flow
  2. import * as React from 'react';
  3. import {readData} from "../utils/WebData";
  4. import i18n from "i18n-js";
  5. import {Snackbar} from 'react-native-paper';
  6. import {RefreshControl, SectionList, View} from "react-native";
  7. import EmptyWebSectionListItem from "./EmptyWebSectionListItem";
  8. type Props = {
  9. navigation: Object,
  10. fetchUrl: string,
  11. autoRefreshTime: number,
  12. refreshOnFocus: boolean,
  13. renderItem: React.Node,
  14. renderSectionHeader: React.Node,
  15. stickyHeader: boolean,
  16. createDataset: Function,
  17. updateData: number,
  18. }
  19. type State = {
  20. refreshing: boolean,
  21. firstLoading: boolean,
  22. fetchedData: Object,
  23. snackbarVisible: boolean
  24. };
  25. const MIN_REFRESH_TIME = 5 * 1000;
  26. /**
  27. * Component used to render a SectionList with data fetched from the web
  28. *
  29. * This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
  30. * To force the component to update, change the value of updateData.
  31. */
  32. export default class WebSectionList extends React.PureComponent<Props, State> {
  33. static defaultProps = {
  34. renderSectionHeader: null,
  35. stickyHeader: false,
  36. updateData: 0,
  37. };
  38. refreshInterval: IntervalID;
  39. lastRefresh: Date;
  40. state = {
  41. refreshing: false,
  42. firstLoading: true,
  43. fetchedData: {},
  44. snackbarVisible: false
  45. };
  46. onRefresh: Function;
  47. onFetchSuccess: Function;
  48. onFetchError: Function;
  49. getEmptyRenderItem: Function;
  50. getEmptySectionHeader: Function;
  51. showSnackBar: Function;
  52. hideSnackBar: Function;
  53. constructor() {
  54. super();
  55. // creating references to functions used in render()
  56. this.onRefresh = this.onRefresh.bind(this);
  57. this.onFetchSuccess = this.onFetchSuccess.bind(this);
  58. this.onFetchError = this.onFetchError.bind(this);
  59. this.getEmptyRenderItem = this.getEmptyRenderItem.bind(this);
  60. this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
  61. this.showSnackBar = this.showSnackBar.bind(this);
  62. this.hideSnackBar = this.hideSnackBar.bind(this);
  63. }
  64. /**
  65. * Registers react navigation events on first screen load.
  66. * Allows to detect when the screen is focused
  67. */
  68. componentDidMount() {
  69. const onScreenFocus = this.onScreenFocus.bind(this);
  70. const onScreenBlur = this.onScreenBlur.bind(this);
  71. this.props.navigation.addListener('focus', onScreenFocus);
  72. this.props.navigation.addListener('blur', onScreenBlur);
  73. this.onRefresh();
  74. }
  75. /**
  76. * Refreshes data when focusing the screen and setup a refresh interval if asked to
  77. */
  78. onScreenFocus() {
  79. if (this.props.refreshOnFocus && this.lastRefresh !== undefined)
  80. this.onRefresh();
  81. if (this.props.autoRefreshTime > 0)
  82. this.refreshInterval = setInterval(this.onRefresh, this.props.autoRefreshTime)
  83. }
  84. /**
  85. * Removes any interval on un-focus
  86. */
  87. onScreenBlur() {
  88. clearInterval(this.refreshInterval);
  89. }
  90. /**
  91. * Callback used when fetch is successful.
  92. * It will update the displayed data and stop the refresh animation
  93. *
  94. * @param fetchedData The newly fetched data
  95. */
  96. onFetchSuccess(fetchedData: Object) {
  97. this.setState({
  98. fetchedData: fetchedData,
  99. refreshing: false,
  100. firstLoading: false
  101. });
  102. this.lastRefresh = new Date();
  103. }
  104. /**
  105. * Callback used when fetch encountered an error.
  106. * It will reset the displayed data and show an error.
  107. */
  108. onFetchError() {
  109. this.setState({
  110. fetchedData: {},
  111. refreshing: false,
  112. firstLoading: false
  113. });
  114. this.showSnackBar();
  115. }
  116. /**
  117. * Refreshes data and shows an animations while doing it
  118. */
  119. onRefresh() {
  120. let canRefresh;
  121. if (this.lastRefresh !== undefined)
  122. canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME;
  123. else
  124. canRefresh = true;
  125. if (canRefresh) {
  126. this.setState({refreshing: true});
  127. readData(this.props.fetchUrl)
  128. .then(this.onFetchSuccess)
  129. .catch(this.onFetchError);
  130. }
  131. }
  132. /**
  133. * Gets an empty section header
  134. *
  135. * @param section The current section
  136. * @return {*}
  137. */
  138. getEmptySectionHeader({section}: Object) {
  139. return <View/>;
  140. }
  141. /**
  142. * Gets an empty render item
  143. *
  144. * @param item The data to display
  145. * @return {*}
  146. */
  147. getEmptyRenderItem({item}: Object) {
  148. return (
  149. <EmptyWebSectionListItem
  150. text={item.text}
  151. icon={item.icon}
  152. refreshing={this.state.refreshing}
  153. />
  154. );
  155. }
  156. /**
  157. * Creates an empty dataset
  158. *
  159. * @return {*}
  160. */
  161. createEmptyDataset() {
  162. return [
  163. {
  164. title: '',
  165. data: [
  166. {
  167. text: this.state.refreshing ?
  168. i18n.t('general.loading') :
  169. i18n.t('general.networkError'),
  170. isSpinner: this.state.refreshing,
  171. icon: this.state.refreshing ?
  172. 'refresh' :
  173. 'access-point-network-off'
  174. }
  175. ],
  176. keyExtractor: this.datasetKeyExtractor,
  177. }
  178. ];
  179. }
  180. /**
  181. * Extracts a key from the given item
  182. *
  183. * @param item The item to extract the key from
  184. * @return {string} The extracted key
  185. */
  186. datasetKeyExtractor(item: Object): string {
  187. return item.text
  188. }
  189. /**
  190. * Shows the error popup
  191. */
  192. showSnackBar() {
  193. this.setState({snackbarVisible: true})
  194. }
  195. /**
  196. * Hides the error popup
  197. */
  198. hideSnackBar() {
  199. this.setState({snackbarVisible: false})
  200. }
  201. render() {
  202. let dataset = this.props.createDataset(this.state.fetchedData);
  203. const isEmpty = dataset[0].data.length === 0;
  204. const shouldRenderHeader = !isEmpty && (this.props.renderSectionHeader !== null);
  205. if (isEmpty)
  206. dataset = this.createEmptyDataset();
  207. return (
  208. <View>
  209. <Snackbar
  210. visible={this.state.snackbarVisible}
  211. onDismiss={this.hideSnackBar}
  212. action={{
  213. label: 'OK',
  214. onPress: this.hideSnackBar,
  215. }}
  216. duration={4000}
  217. >
  218. {i18n.t("homeScreen.listUpdateFail")}
  219. </Snackbar>
  220. <SectionList
  221. sections={dataset}
  222. refreshControl={
  223. <RefreshControl
  224. refreshing={this.state.refreshing}
  225. onRefresh={this.onRefresh}
  226. />
  227. }
  228. //$FlowFixMe
  229. renderSectionHeader={shouldRenderHeader ? this.props.renderSectionHeader : this.getEmptySectionHeader}
  230. //$FlowFixMe
  231. renderItem={isEmpty ? this.getEmptyRenderItem : this.props.renderItem}
  232. style={{minHeight: 300, width: '100%'}}
  233. stickySectionHeadersEnabled={this.props.stickyHeader}
  234. contentContainerStyle={
  235. isEmpty ?
  236. {flexGrow: 1, justifyContent: 'center', alignItems: 'center'} : {}
  237. }
  238. />
  239. </View>
  240. );
  241. }
  242. }