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.

FetchedDataSectionList.js 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. // @flow
  2. import * as React from 'react';
  3. import WebDataManager from "../utils/WebDataManager";
  4. import {H3, Spinner, Tab, TabHeading, Tabs, Text} from "native-base";
  5. import {RefreshControl, SectionList, View} from "react-native";
  6. import CustomMaterialIcon from "./CustomMaterialIcon";
  7. import i18n from 'i18n-js';
  8. import ThemeManager from "../utils/ThemeManager";
  9. import BaseContainer from "./BaseContainer";
  10. type Props = {
  11. navigation: Object,
  12. }
  13. type State = {
  14. refreshing: boolean,
  15. firstLoading: boolean,
  16. fetchedData: Object,
  17. machinesWatched: Array<Object>,
  18. };
  19. /**
  20. * Class used to create a basic list view using online json data.
  21. * Used by inheriting from it and redefining getters.
  22. */
  23. export default class FetchedDataSectionList extends React.Component<Props, State> {
  24. webDataManager: WebDataManager;
  25. willFocusSubscription: function;
  26. willBlurSubscription: function;
  27. refreshInterval: IntervalID;
  28. refreshTime: number;
  29. constructor(fetchUrl: string, refreshTime: number) {
  30. super();
  31. this.webDataManager = new WebDataManager(fetchUrl);
  32. this.refreshTime = refreshTime;
  33. }
  34. state = {
  35. refreshing: false,
  36. firstLoading: true,
  37. fetchedData: {},
  38. machinesWatched: [],
  39. };
  40. /**
  41. * Get the translation for the header in the current language
  42. * @return {string}
  43. */
  44. getHeaderTranslation(): string {
  45. return "Header";
  46. }
  47. /**
  48. * Get the translation for the toasts in the current language
  49. * @return {string}
  50. */
  51. getUpdateToastTranslations(): Array<string> {
  52. return ["whoa", "nah"];
  53. }
  54. /**
  55. * Register react navigation events on first screen load.
  56. * Allows to detect when the screen is focused
  57. */
  58. componentDidMount() {
  59. this.willFocusSubscription = this.props.navigation.addListener(
  60. 'willFocus',
  61. payload => {
  62. this.onScreenFocus();
  63. }
  64. );
  65. this.willBlurSubscription = this.props.navigation.addListener(
  66. 'willBlur',
  67. payload => {
  68. this.onScreenBlur();
  69. }
  70. );
  71. }
  72. /**
  73. * Refresh data when focusing the screen and setup a refresh interval if asked to
  74. */
  75. onScreenFocus() {
  76. this._onRefresh();
  77. if (this.refreshTime > 0)
  78. this.refreshInterval = setInterval(() => this._onRefresh(), this.refreshTime)
  79. }
  80. /**
  81. * Remove any interval on un-focus
  82. */
  83. onScreenBlur() {
  84. clearInterval(this.refreshInterval);
  85. }
  86. /**
  87. * Unregister from event when un-mounting components
  88. */
  89. componentWillUnmount() {
  90. if (this.willBlurSubscription !== undefined)
  91. this.willBlurSubscription.remove();
  92. if (this.willFocusSubscription !== undefined)
  93. this.willFocusSubscription.remove();
  94. }
  95. /**
  96. * Refresh data and show a toast if any error occurred
  97. * @private
  98. */
  99. _onRefresh = () => {
  100. this.setState({refreshing: true});
  101. this.webDataManager.readData().then((fetchedData) => {
  102. this.setState({
  103. fetchedData: fetchedData,
  104. refreshing: false,
  105. firstLoading: false
  106. });
  107. this.webDataManager.showUpdateToast(this.getUpdateToastTranslations()[0], this.getUpdateToastTranslations()[1]);
  108. });
  109. };
  110. /**
  111. * Get the render item to be used for display in the list.
  112. * Must be overridden by inheriting class.
  113. *
  114. * @param item
  115. * @param section
  116. * @param data
  117. * @return {*}
  118. */
  119. getRenderItem(item: Object, section: Object, data: Object) {
  120. return <View/>;
  121. }
  122. /**
  123. * Get the render item to be used for the section title in the list.
  124. * Must be overridden by inheriting class.
  125. *
  126. * @param title
  127. * @return {*}
  128. */
  129. getRenderSectionHeader(title: String) {
  130. return <View/>;
  131. }
  132. /**
  133. * Get the render item to be used when the list is empty.
  134. * No need to be overridden, has good defaults.
  135. *
  136. * @param text
  137. * @param isSpinner
  138. * @param icon
  139. * @return {*}
  140. */
  141. getEmptyRenderItem(text: string, isSpinner: boolean, icon: string) {
  142. return (
  143. <View>
  144. <View style={{
  145. justifyContent: 'center',
  146. alignItems: 'center',
  147. width: '100%',
  148. height: 100,
  149. marginBottom: 20
  150. }}>
  151. {isSpinner ?
  152. <Spinner/>
  153. :
  154. <CustomMaterialIcon
  155. icon={icon}
  156. fontSize={100}
  157. width={100}
  158. color={ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText}/>}
  159. </View>
  160. <H3 style={{
  161. textAlign: 'center',
  162. marginRight: 20,
  163. marginLeft: 20,
  164. color: ThemeManager.getCurrentThemeVariables().fetchedDataSectionListErrorText
  165. }}>
  166. {text}
  167. </H3>
  168. </View>);
  169. }
  170. /**
  171. * Create the dataset to be used in the list from the data fetched.
  172. * Must be overridden.
  173. *
  174. * @param fetchedData {Object}
  175. * @return {Array}
  176. */
  177. createDataset(fetchedData: Object): Array<Object> {
  178. return [];
  179. }
  180. /**
  181. * Create the dataset when no fetched data is available.
  182. * No need to be overridden, has good defaults.
  183. *
  184. * @return
  185. */
  186. createEmptyDataset() {
  187. return [
  188. {
  189. title: '',
  190. data: [
  191. {
  192. text: this.state.refreshing ?
  193. i18n.t('general.loading') :
  194. i18n.t('general.networkError'),
  195. isSpinner: this.state.refreshing,
  196. icon: this.state.refreshing ?
  197. 'refresh' :
  198. 'access-point-network-off'
  199. }
  200. ],
  201. keyExtractor: (item: Object) => item.text,
  202. }
  203. ];
  204. }
  205. /**
  206. * Should the app use a tab layout instead of a section list ?
  207. * If yes, each section will be rendered in a new tab.
  208. * Can be overridden.
  209. *
  210. * @return {boolean}
  211. */
  212. hasTabs() {
  213. return false;
  214. }
  215. /**
  216. * Get the section list render using the generated dataset
  217. *
  218. * @param dataset
  219. * @return
  220. */
  221. getSectionList(dataset: Array<Object>) {
  222. let isEmpty = dataset[0].data.length === 0;
  223. if (isEmpty)
  224. dataset = this.createEmptyDataset();
  225. return (
  226. <SectionList
  227. sections={dataset}
  228. refreshControl={
  229. <RefreshControl
  230. refreshing={this.state.refreshing}
  231. onRefresh={this._onRefresh}
  232. />
  233. }
  234. renderSectionHeader={({section: {title}}) =>
  235. isEmpty ?
  236. <View/> :
  237. this.getRenderSectionHeader(title)
  238. }
  239. renderItem={({item, section}) =>
  240. isEmpty ?
  241. this.getEmptyRenderItem(item.text, item.isSpinner, item.icon) :
  242. this.getRenderItem(item, section, dataset)
  243. }
  244. style={{minHeight: 300, width: '100%'}}
  245. contentContainerStyle={
  246. isEmpty ?
  247. {flexGrow: 1, justifyContent: 'center', alignItems: 'center'} : {}
  248. }
  249. />
  250. );
  251. }
  252. /**
  253. * Generate the tabs containing the lists
  254. *
  255. * @param dataset
  256. * @return
  257. */
  258. getTabbedView(dataset: Array<Object>) {
  259. let tabbedView = [];
  260. for (let i = 0; i < dataset.length; i++) {
  261. tabbedView.push(
  262. <Tab heading={
  263. <TabHeading>
  264. <CustomMaterialIcon icon={dataset[i].icon}
  265. color={'#fff'}
  266. fontSize={20}
  267. />
  268. <Text>{dataset[i].title}</Text>
  269. </TabHeading>}
  270. key={dataset[i].title}
  271. style={{backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor}}>
  272. {this.getSectionList(
  273. [
  274. {
  275. title: dataset[i].title,
  276. data: dataset[i].data,
  277. extraData: dataset[i].extraData,
  278. keyExtractor: dataset[i].keyExtractor
  279. }
  280. ]
  281. )}
  282. </Tab>);
  283. }
  284. return tabbedView;
  285. }
  286. render() {
  287. const nav = this.props.navigation;
  288. const dataset = this.createDataset(this.state.fetchedData);
  289. return (
  290. <BaseContainer navigation={nav} headerTitle={this.getHeaderTranslation()}>
  291. {this.hasTabs() ?
  292. <Tabs>
  293. {this.getTabbedView(dataset)}
  294. </Tabs>
  295. :
  296. this.getSectionList(dataset)
  297. }
  298. </BaseContainer>
  299. );
  300. }
  301. }