226 lines
6.2 KiB
TypeScript
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;
|