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 10KB

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