Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
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 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // @flow
  2. import * as React from 'react';
  3. import ThemeManager from '../utils/ThemeManager';
  4. import WebDataManager from "../utils/WebDataManager";
  5. import {MaterialCommunityIcons} from "@expo/vector-icons";
  6. import i18n from "i18n-js";
  7. import {ActivityIndicator, Subheading} from 'react-native-paper';
  8. import {RefreshControl, SectionList, View} from "react-native";
  9. type Props = {
  10. navigation: Object,
  11. fetchUrl: string,
  12. refreshTime: number,
  13. renderItem: React.Node,
  14. renderSectionHeader: React.Node,
  15. stickyHeader: boolean,
  16. createDataset: Function,
  17. updateErrorText: string,
  18. }
  19. type State = {
  20. refreshing: boolean,
  21. firstLoading: boolean,
  22. fetchedData: Object,
  23. };
  24. /**
  25. * Custom component defining a material icon using native base
  26. *
  27. * @prop active {boolean} Whether to set the icon color to active
  28. * @prop icon {string} The icon string to use from MaterialCommunityIcons
  29. * @prop color {string} The icon color. Use default theme color if unspecified
  30. * @prop fontSize {number} The icon size. Use 26 if unspecified
  31. * @prop width {number} The icon width. Use 30 if unspecified
  32. */
  33. export default class WebSectionList extends React.Component<Props, State> {
  34. static defaultProps = {
  35. renderSectionHeader: undefined,
  36. stickyHeader: false,
  37. };
  38. webDataManager: WebDataManager;
  39. refreshInterval: IntervalID;
  40. lastRefresh: Date;
  41. state = {
  42. refreshing: false,
  43. firstLoading: true,
  44. fetchedData: {},
  45. };
  46. onRefresh: Function;
  47. onFetchSuccess: Function;
  48. onFetchError: Function;
  49. getEmptyRenderItem: Function;
  50. getEmptySectionHeader: Function;
  51. constructor() {
  52. super();
  53. // creating references to functions used in render()
  54. this.onRefresh = this.onRefresh.bind(this);
  55. this.onFetchSuccess = this.onFetchSuccess.bind(this);
  56. this.onFetchError = this.onFetchError.bind(this);
  57. this.getEmptyRenderItem = this.getEmptyRenderItem.bind(this);
  58. this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
  59. }
  60. /**
  61. * Register react navigation events on first screen load.
  62. * Allows to detect when the screen is focused
  63. */
  64. componentDidMount() {
  65. this.webDataManager = new WebDataManager(this.props.fetchUrl);
  66. const onScreenFocus = this.onScreenFocus.bind(this);
  67. const onScreenBlur = this.onScreenBlur.bind(this);
  68. this.props.navigation.addListener('focus', onScreenFocus);
  69. this.props.navigation.addListener('blur', onScreenBlur);
  70. }
  71. /**
  72. * Refresh data when focusing the screen and setup a refresh interval if asked to
  73. */
  74. onScreenFocus() {
  75. this.onRefresh();
  76. if (this.props.refreshTime > 0)
  77. this.refreshInterval = setInterval(this.onRefresh, this.props.refreshTime)
  78. }
  79. /**
  80. * Remove any interval on un-focus
  81. */
  82. onScreenBlur() {
  83. clearInterval(this.refreshInterval);
  84. }
  85. onFetchSuccess(fetchedData: Object) {
  86. this.setState({
  87. fetchedData: fetchedData,
  88. refreshing: false,
  89. firstLoading: false
  90. });
  91. this.lastRefresh = new Date();
  92. }
  93. onFetchError() {
  94. this.setState({
  95. fetchedData: {},
  96. refreshing: false,
  97. firstLoading: false
  98. });
  99. this.webDataManager.showUpdateToast(this.props.updateErrorText);
  100. }
  101. /**
  102. * Refresh data and show a toast if any error occurred
  103. * @private
  104. */
  105. onRefresh() {
  106. let canRefresh;
  107. if (this.lastRefresh !== undefined)
  108. canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > this.props.refreshTime;
  109. else
  110. canRefresh = true;
  111. if (canRefresh) {
  112. this.setState({refreshing: true});
  113. this.webDataManager.readData()
  114. .then(this.onFetchSuccess)
  115. .catch(this.onFetchError);
  116. }
  117. }
  118. getEmptySectionHeader({section}: Object) {
  119. return <View/>;
  120. }
  121. getEmptyRenderItem({item}: Object) {
  122. return (
  123. <View>
  124. <View style={{
  125. justifyContent: 'center',
  126. alignItems: 'center',
  127. width: '100%',
  128. height: 100,
  129. marginBottom: 20
  130. }}>
  131. {this.state.refreshing ?
  132. <ActivityIndicator
  133. animating={true}
  134. size={'large'}
  135. color={ThemeManager.getCurrentThemeVariables().primary}/>
  136. :
  137. <MaterialCommunityIcons
  138. name={item.icon}
  139. size={100}
  140. color={ThemeManager.getCurrentThemeVariables().textDisabled}/>}
  141. </View>
  142. <Subheading style={{
  143. textAlign: 'center',
  144. marginRight: 20,
  145. marginLeft: 20,
  146. color: ThemeManager.getCurrentThemeVariables().textDisabled
  147. }}>
  148. {item.text}
  149. </Subheading>
  150. </View>);
  151. }
  152. createEmptyDataset() {
  153. return [
  154. {
  155. title: '',
  156. data: [
  157. {
  158. text: this.state.refreshing ?
  159. i18n.t('general.loading') :
  160. i18n.t('general.networkError'),
  161. isSpinner: this.state.refreshing,
  162. icon: this.state.refreshing ?
  163. 'refresh' :
  164. 'access-point-network-off'
  165. }
  166. ],
  167. keyExtractor: this.datasetKeyExtractor,
  168. }
  169. ];
  170. }
  171. datasetKeyExtractor(item: Object) {
  172. return item.text
  173. }
  174. render() {
  175. let dataset = this.props.createDataset(this.state.fetchedData);
  176. const isEmpty = dataset[0].data.length === 0;
  177. const shouldRenderHeader = isEmpty || (this.props.renderSectionHeader !== undefined);
  178. if (isEmpty)
  179. dataset = this.createEmptyDataset();
  180. return (
  181. <SectionList
  182. sections={dataset}
  183. refreshControl={
  184. <RefreshControl
  185. refreshing={this.state.refreshing}
  186. onRefresh={this.onRefresh}
  187. />
  188. }
  189. renderSectionHeader={shouldRenderHeader ? this.getEmptySectionHeader : this.props.renderSectionHeader}
  190. renderItem={isEmpty ? this.getEmptyRenderItem : this.props.renderItem}
  191. style={{minHeight: 300, width: '100%'}}
  192. stickySectionHeadersEnabled={this.props.stickyHeader}
  193. contentContainerStyle={
  194. isEmpty ?
  195. {flexGrow: 1, justifyContent: 'center', alignItems: 'center'} : {}
  196. }
  197. />
  198. );
  199. }
  200. }