application-amicale/src/components/Screens/WebSectionList.tsx
2021-05-10 15:14:55 +02:00

226 lines
6.2 KiB
TypeScript

/*
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
*
* This file is part of Campus INSAT.
*
* Campus INSAT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Campus INSAT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { useState } from 'react';
import i18n from 'i18n-js';
import { Snackbar } from 'react-native-paper';
import {
NativeScrollEvent,
NativeSyntheticEvent,
RefreshControl,
SectionListData,
SectionListRenderItemInfo,
StyleSheet,
View,
} from 'react-native';
import * as Animatable from 'react-native-animatable';
import ErrorView from './ErrorView';
import BasicLoadingScreen from './BasicLoadingScreen';
import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
import { ERROR_TYPE } from '../../utils/WebData';
import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
import GENERAL_STYLES from '../../constants/Styles';
import RequestScreen from './RequestScreen';
export type SectionListDataType<ItemT> = Array<{
title: string;
icon?: string;
data: Array<ItemT>;
keyExtractor?: (data: ItemT) => string;
}>;
type Props<ItemT, RawData> = {
request: () => Promise<RawData>;
refreshOnFocus: boolean;
renderItem: (data: SectionListRenderItemInfo<ItemT>) => React.ReactNode;
createDataset: (
data: RawData | undefined,
isLoading: boolean
) => SectionListDataType<ItemT>;
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
showError?: boolean;
itemHeight?: number | null;
autoRefreshTime?: number;
updateData?: number | string;
renderListHeaderComponent?: (
data?: RawData
) => React.ComponentType<any> | React.ReactElement | null;
renderSectionHeader?: (
data: { section: SectionListData<ItemT> },
isLoading: boolean
) => React.ReactElement | null;
stickyHeader?: boolean;
cache?: RawData;
onCacheUpdate?: (newCache: RawData) => void;
};
const styles = StyleSheet.create({
container: {
minHeight: '100%',
},
});
/**
* Component used to render a SectionList with data fetched from the web
* To force the component to update, change the value of updateData.
*/
function WebSectionList<ItemT, RawData>(props: Props<ItemT, RawData>) {
const [snackbarVisible, setSnackbarVisible] = useState(false);
const showSnackBar = () => setSnackbarVisible(true);
const hideSnackBar = () => setSnackbarVisible(false);
const getItemLayout = (
height: number,
_data: Array<SectionListData<ItemT>> | null,
index: number
): { length: number; offset: number; index: number } => {
return {
length: height,
offset: height * index,
index,
};
};
const getRenderSectionHeader = (
data: { section: SectionListData<ItemT> },
loading: boolean
) => {
const { renderSectionHeader } = props;
if (renderSectionHeader) {
return (
<Animatable.View
animation={'fadeInUp'}
duration={500}
useNativeDriver={true}
>
{renderSectionHeader(data, loading)}
</Animatable.View>
);
}
return null;
};
const getRenderItem = (data: SectionListRenderItemInfo<ItemT>) => {
const { renderItem } = props;
return (
<Animatable.View
animation={'fadeInUp'}
duration={500}
useNativeDriver={true}
>
{renderItem(data)}
</Animatable.View>
);
};
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
if (props.onScroll) {
props.onScroll(event);
}
};
const render = (
data: RawData | undefined,
loading: boolean,
refreshData: (newRequest?: () => Promise<RawData>) => void
) => {
const { itemHeight } = props;
const dataset = props.createDataset(data, loading);
if (!data && !loading) {
showSnackBar();
}
return (
<CollapsibleSectionList
sections={dataset}
extraData={props.updateData}
paddedProps={(paddingTop) => ({
refreshControl: (
<RefreshControl
progressViewOffset={paddingTop}
refreshing={loading}
onRefresh={refreshData}
/>
),
})}
renderSectionHeader={(info) => getRenderSectionHeader(info, loading)}
renderItem={getRenderItem}
stickySectionHeadersEnabled={props.stickyHeader}
style={styles.container}
ListHeaderComponent={
props.renderListHeaderComponent != null
? props.renderListHeaderComponent(data)
: null
}
ListEmptyComponent={
loading ? (
<BasicLoadingScreen />
) : (
<ErrorView
status={ERROR_TYPE.CONNECTION_ERROR}
button={{
icon: 'refresh',
text: i18n.t('general.retry'),
onPress: refreshData,
}}
/>
)
}
getItemLayout={
itemHeight ? (d, i) => getItemLayout(itemHeight, d, i) : undefined
}
onScroll={onScroll}
hasTab={true}
/>
);
};
return (
<View style={GENERAL_STYLES.flex}>
<RequestScreen<RawData>
request={props.request}
render={render}
showError={false}
showLoading={false}
autoRefreshTime={props.autoRefreshTime}
refreshOnFocus={props.refreshOnFocus}
cache={props.cache}
onCacheUpdate={props.onCacheUpdate}
/>
<Snackbar
visible={snackbarVisible}
onDismiss={hideSnackBar}
action={{
label: 'OK',
onPress: hideSnackBar,
}}
duration={4000}
style={{
bottom: TAB_BAR_HEIGHT,
}}
>
{i18n.t('general.listUpdateFail')}
</Snackbar>
</View>
);
}
export default WebSectionList;