2020-03-05 19:54:56 +01:00
|
|
|
// @flow
|
|
|
|
|
|
|
|
import * as React from 'react';
|
2020-04-02 10:07:20 +02:00
|
|
|
import {readData} from "../../utils/WebData";
|
2020-03-05 19:54:56 +01:00
|
|
|
import i18n from "i18n-js";
|
2020-03-07 11:49:32 +01:00
|
|
|
import {Snackbar} from 'react-native-paper';
|
2020-03-06 23:15:01 +01:00
|
|
|
import {RefreshControl, SectionList, View} from "react-native";
|
2020-04-03 14:28:33 +02:00
|
|
|
import NetworkErrorComponent from "../Custom/NetworkErrorComponent";
|
|
|
|
import BasicLoadingScreen from "../Custom/BasicLoadingScreen";
|
2020-03-05 19:54:56 +01:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
navigation: Object,
|
|
|
|
fetchUrl: string,
|
2020-03-08 15:50:34 +01:00
|
|
|
autoRefreshTime: number,
|
|
|
|
refreshOnFocus: boolean,
|
2020-03-05 19:54:56 +01:00
|
|
|
renderItem: React.Node,
|
|
|
|
renderSectionHeader: React.Node,
|
|
|
|
stickyHeader: boolean,
|
|
|
|
createDataset: Function,
|
2020-03-10 16:43:52 +01:00
|
|
|
updateData: number,
|
2020-03-05 19:54:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type State = {
|
|
|
|
refreshing: boolean,
|
|
|
|
firstLoading: boolean,
|
2020-04-03 14:28:33 +02:00
|
|
|
fetchedData: ?Object,
|
2020-03-07 09:15:25 +01:00
|
|
|
snackbarVisible: boolean
|
2020-03-05 19:54:56 +01:00
|
|
|
};
|
|
|
|
|
2020-03-08 15:50:34 +01:00
|
|
|
|
2020-03-29 14:46:44 +02:00
|
|
|
const MIN_REFRESH_TIME = 5 * 1000;
|
2020-03-05 19:54:56 +01:00
|
|
|
/**
|
2020-03-29 14:46:44 +02:00
|
|
|
* Component used to render a SectionList with data fetched from the web
|
|
|
|
*
|
2020-03-10 16:43:52 +01:00
|
|
|
* This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
|
|
|
|
* To force the component to update, change the value of updateData.
|
2020-03-05 19:54:56 +01:00
|
|
|
*/
|
2020-03-09 23:15:13 +01:00
|
|
|
export default class WebSectionList extends React.PureComponent<Props, State> {
|
2020-03-05 19:54:56 +01:00
|
|
|
|
|
|
|
static defaultProps = {
|
2020-03-07 09:15:25 +01:00
|
|
|
renderSectionHeader: null,
|
2020-03-05 19:54:56 +01:00
|
|
|
stickyHeader: false,
|
2020-03-22 17:02:00 +01:00
|
|
|
updateData: 0,
|
2020-03-05 19:54:56 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
refreshInterval: IntervalID;
|
|
|
|
lastRefresh: Date;
|
|
|
|
|
|
|
|
state = {
|
|
|
|
refreshing: false,
|
|
|
|
firstLoading: true,
|
2020-04-03 14:28:33 +02:00
|
|
|
fetchedData: undefined,
|
2020-03-07 09:15:25 +01:00
|
|
|
snackbarVisible: false
|
2020-03-05 19:54:56 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
onRefresh: Function;
|
|
|
|
onFetchSuccess: Function;
|
|
|
|
onFetchError: Function;
|
|
|
|
getEmptySectionHeader: Function;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
// creating references to functions used in render()
|
|
|
|
this.onRefresh = this.onRefresh.bind(this);
|
|
|
|
this.onFetchSuccess = this.onFetchSuccess.bind(this);
|
|
|
|
this.onFetchError = this.onFetchError.bind(this);
|
|
|
|
this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-03-29 14:46:44 +02:00
|
|
|
* Registers react navigation events on first screen load.
|
2020-03-05 19:54:56 +01:00
|
|
|
* Allows to detect when the screen is focused
|
|
|
|
*/
|
|
|
|
componentDidMount() {
|
|
|
|
const onScreenFocus = this.onScreenFocus.bind(this);
|
|
|
|
const onScreenBlur = this.onScreenBlur.bind(this);
|
|
|
|
this.props.navigation.addListener('focus', onScreenFocus);
|
|
|
|
this.props.navigation.addListener('blur', onScreenBlur);
|
2020-03-08 15:50:34 +01:00
|
|
|
this.onRefresh();
|
2020-03-05 19:54:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-03-29 14:46:44 +02:00
|
|
|
* Refreshes data when focusing the screen and setup a refresh interval if asked to
|
2020-03-05 19:54:56 +01:00
|
|
|
*/
|
|
|
|
onScreenFocus() {
|
2020-03-08 15:50:34 +01:00
|
|
|
if (this.props.refreshOnFocus && this.lastRefresh !== undefined)
|
|
|
|
this.onRefresh();
|
|
|
|
if (this.props.autoRefreshTime > 0)
|
|
|
|
this.refreshInterval = setInterval(this.onRefresh, this.props.autoRefreshTime)
|
2020-03-05 19:54:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-03-29 14:46:44 +02:00
|
|
|
* Removes any interval on un-focus
|
2020-03-05 19:54:56 +01:00
|
|
|
*/
|
|
|
|
onScreenBlur() {
|
|
|
|
clearInterval(this.refreshInterval);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-03-29 14:46:44 +02:00
|
|
|
/**
|
|
|
|
* Callback used when fetch is successful.
|
|
|
|
* It will update the displayed data and stop the refresh animation
|
|
|
|
*
|
|
|
|
* @param fetchedData The newly fetched data
|
|
|
|
*/
|
2020-03-05 19:54:56 +01:00
|
|
|
onFetchSuccess(fetchedData: Object) {
|
|
|
|
this.setState({
|
|
|
|
fetchedData: fetchedData,
|
|
|
|
refreshing: false,
|
|
|
|
firstLoading: false
|
|
|
|
});
|
|
|
|
this.lastRefresh = new Date();
|
|
|
|
}
|
|
|
|
|
2020-03-29 14:46:44 +02:00
|
|
|
/**
|
|
|
|
* Callback used when fetch encountered an error.
|
|
|
|
* It will reset the displayed data and show an error.
|
|
|
|
*/
|
2020-03-05 19:54:56 +01:00
|
|
|
onFetchError() {
|
|
|
|
this.setState({
|
2020-04-03 14:28:33 +02:00
|
|
|
fetchedData: undefined,
|
2020-03-05 19:54:56 +01:00
|
|
|
refreshing: false,
|
|
|
|
firstLoading: false
|
|
|
|
});
|
2020-03-07 09:15:25 +01:00
|
|
|
this.showSnackBar();
|
2020-03-05 19:54:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-03-29 14:46:44 +02:00
|
|
|
* Refreshes data and shows an animations while doing it
|
2020-03-05 19:54:56 +01:00
|
|
|
*/
|
|
|
|
onRefresh() {
|
|
|
|
let canRefresh;
|
|
|
|
if (this.lastRefresh !== undefined)
|
2020-03-08 15:50:34 +01:00
|
|
|
canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) > MIN_REFRESH_TIME;
|
2020-03-05 19:54:56 +01:00
|
|
|
else
|
|
|
|
canRefresh = true;
|
|
|
|
if (canRefresh) {
|
|
|
|
this.setState({refreshing: true});
|
2020-03-30 15:28:08 +02:00
|
|
|
readData(this.props.fetchUrl)
|
2020-03-05 19:54:56 +01:00
|
|
|
.then(this.onFetchSuccess)
|
|
|
|
.catch(this.onFetchError);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-29 14:46:44 +02:00
|
|
|
/**
|
|
|
|
* Gets an empty section header
|
|
|
|
*
|
|
|
|
* @param section The current section
|
|
|
|
* @return {*}
|
|
|
|
*/
|
2020-03-05 19:54:56 +01:00
|
|
|
getEmptySectionHeader({section}: Object) {
|
|
|
|
return <View/>;
|
|
|
|
}
|
|
|
|
|
2020-03-29 14:46:44 +02:00
|
|
|
/**
|
|
|
|
* Shows the error popup
|
|
|
|
*/
|
2020-04-04 19:42:04 +02:00
|
|
|
showSnackBar = () => this.setState({snackbarVisible: true});
|
2020-03-07 09:15:25 +01:00
|
|
|
|
2020-03-29 14:46:44 +02:00
|
|
|
/**
|
|
|
|
* Hides the error popup
|
|
|
|
*/
|
2020-04-04 19:42:04 +02:00
|
|
|
hideSnackBar = () => this.setState({snackbarVisible: false});
|
2020-03-07 09:15:25 +01:00
|
|
|
|
2020-03-05 19:54:56 +01:00
|
|
|
render() {
|
2020-04-03 14:28:33 +02:00
|
|
|
let dataset = [];
|
|
|
|
if (this.state.fetchedData !== undefined)
|
|
|
|
dataset = this.props.createDataset(this.state.fetchedData);
|
|
|
|
const shouldRenderHeader = this.props.renderSectionHeader !== null;
|
2020-03-05 19:54:56 +01:00
|
|
|
return (
|
2020-03-07 09:15:25 +01:00
|
|
|
<View>
|
2020-04-03 14:28:33 +02:00
|
|
|
{/*$FlowFixMe*/}
|
2020-03-07 09:15:25 +01:00
|
|
|
<SectionList
|
|
|
|
sections={dataset}
|
2020-04-04 21:36:58 +02:00
|
|
|
extraData={this.props.updateData}
|
2020-03-07 09:15:25 +01:00
|
|
|
refreshControl={
|
|
|
|
<RefreshControl
|
|
|
|
refreshing={this.state.refreshing}
|
|
|
|
onRefresh={this.onRefresh}
|
|
|
|
/>
|
|
|
|
}
|
2020-03-22 17:02:00 +01:00
|
|
|
//$FlowFixMe
|
2020-03-07 09:15:25 +01:00
|
|
|
renderSectionHeader={shouldRenderHeader ? this.props.renderSectionHeader : this.getEmptySectionHeader}
|
2020-03-22 17:02:00 +01:00
|
|
|
//$FlowFixMe
|
2020-04-03 14:28:33 +02:00
|
|
|
renderItem={this.props.renderItem}
|
2020-03-07 09:15:25 +01:00
|
|
|
stickySectionHeadersEnabled={this.props.stickyHeader}
|
2020-04-03 14:28:33 +02:00
|
|
|
contentContainerStyle={{minHeight: '100%'}}
|
|
|
|
style={{minHeight: '100%'}}
|
|
|
|
ListEmptyComponent={this.state.refreshing
|
|
|
|
? <BasicLoadingScreen/>
|
|
|
|
: <NetworkErrorComponent
|
|
|
|
message={i18n.t('general.networkError')}
|
|
|
|
icon={"access-point-network-off"}
|
|
|
|
onRefresh={this.onRefresh}/>
|
2020-03-07 09:15:25 +01:00
|
|
|
}
|
|
|
|
/>
|
2020-04-04 19:42:04 +02:00
|
|
|
<Snackbar
|
|
|
|
visible={this.state.snackbarVisible}
|
|
|
|
onDismiss={this.hideSnackBar}
|
|
|
|
action={{
|
|
|
|
label: 'OK',
|
|
|
|
onPress: () => {},
|
|
|
|
}}
|
|
|
|
duration={4000}
|
|
|
|
>
|
|
|
|
{i18n.t("homeScreen.listUpdateFail")}
|
|
|
|
</Snackbar>
|
2020-03-07 09:15:25 +01:00
|
|
|
</View>
|
2020-03-05 19:54:56 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|