diff --git a/src/components/Overrides/CustomHeaderButton.tsx b/src/components/Overrides/CustomHeaderButton.tsx index ded22b4..c6c4efc 100644 --- a/src/components/Overrides/CustomHeaderButton.tsx +++ b/src/components/Overrides/CustomHeaderButton.tsx @@ -39,7 +39,9 @@ const MaterialHeaderButton = (props: HeaderButtonProps) => { ); }; -const MaterialHeaderButtons = (props: HeaderButtonsProps) => { +const MaterialHeaderButtons = ( + props: HeaderButtonsProps & {children?: React.ReactNode}, +) => { return ( ); diff --git a/src/components/Screens/WebSectionList.js b/src/components/Screens/WebSectionList.tsx similarity index 73% rename from src/components/Screens/WebSectionList.js rename to src/components/Screens/WebSectionList.tsx index efb18dc..cee31c3 100644 --- a/src/components/Screens/WebSectionList.js +++ b/src/components/Screens/WebSectionList.tsx @@ -17,12 +17,15 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import i18n from 'i18n-js'; import {Snackbar} from 'react-native-paper'; -import {RefreshControl, View} from 'react-native'; +import { + NativeSyntheticEvent, + RefreshControl, + SectionListData, + View, +} from 'react-native'; import * as Animatable from 'react-native-animatable'; import {Collapsible} from 'react-navigation-collapsible'; import {StackNavigationProp} from '@react-navigation/stack'; @@ -32,42 +35,43 @@ import withCollapsible from '../../utils/withCollapsible'; import CustomTabBar from '../Tabbar/CustomTabBar'; import {ERROR_TYPE, readData} from '../../utils/WebData'; import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList'; -import type {ApiGenericDataType} from '../../utils/WebData'; -export type SectionListDataType = Array<{ - title: string, - data: Array, - keyExtractor?: (T) => string, +export type SectionListDataType = Array<{ + title: string; + data: Array; + keyExtractor?: (data: ItemT) => string; }>; -type PropsType = { - navigation: StackNavigationProp, - fetchUrl: string, - autoRefreshTime: number, - refreshOnFocus: boolean, - renderItem: (data: {item: T}) => React.Node, +type PropsType = { + navigation: StackNavigationProp; + fetchUrl: string; + autoRefreshTime: number; + refreshOnFocus: boolean; + renderItem: (data: {item: ItemT}) => React.ReactNode; createDataset: ( - data: ApiGenericDataType | null, + data: RawData | null, isLoading?: boolean, - ) => SectionListDataType, - onScroll: (event: SyntheticEvent) => void, - collapsibleStack: Collapsible, + ) => SectionListDataType; + onScroll: (event: NativeSyntheticEvent) => void; + collapsibleStack: Collapsible; - showError?: boolean, - itemHeight?: number | null, - updateData?: number, - renderListHeaderComponent?: (data: ApiGenericDataType | null) => React.Node, + showError?: boolean; + itemHeight?: number | null; + updateData?: number; + renderListHeaderComponent?: ( + data: RawData | null, + ) => React.ComponentType | React.ReactElement | null; renderSectionHeader?: ( - data: {section: {title: string}}, + data: {section: SectionListData}, isLoading?: boolean, - ) => React.Node, - stickyHeader?: boolean, + ) => React.ReactElement | null; + stickyHeader?: boolean; }; -type StateType = { - refreshing: boolean, - fetchedData: ApiGenericDataType | null, - snackbarVisible: boolean, +type StateType = { + refreshing: boolean; + fetchedData: RawData | null; + snackbarVisible: boolean; }; const MIN_REFRESH_TIME = 5 * 1000; @@ -78,22 +82,25 @@ const MIN_REFRESH_TIME = 5 * 1000; * 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. */ -class WebSectionList extends React.PureComponent, StateType> { +class WebSectionList extends React.PureComponent< + PropsType, + StateType +> { static defaultProps = { showError: true, itemHeight: null, updateData: 0, - renderListHeaderComponent: (): React.Node => null, - renderSectionHeader: (): React.Node => null, + renderListHeaderComponent: () => null, + renderSectionHeader: () => null, stickyHeader: false, }; - refreshInterval: IntervalID; + refreshInterval: NodeJS.Timeout | undefined; - lastRefresh: Date | null; + lastRefresh: Date | undefined; - constructor() { - super(); + constructor(props: PropsType) { + super(props); this.state = { refreshing: false, fetchedData: null, @@ -109,7 +116,7 @@ class WebSectionList extends React.PureComponent, StateType> { const {navigation} = this.props; navigation.addListener('focus', this.onScreenFocus); navigation.addListener('blur', this.onScreenBlur); - this.lastRefresh = null; + this.lastRefresh = undefined; this.onRefresh(); } @@ -121,15 +128,18 @@ class WebSectionList extends React.PureComponent, StateType> { if (props.refreshOnFocus && this.lastRefresh) { setTimeout(this.onRefresh, 200); } - if (props.autoRefreshTime > 0) + if (props.autoRefreshTime > 0) { this.refreshInterval = setInterval(this.onRefresh, props.autoRefreshTime); + } }; /** * Removes any interval on un-focus */ onScreenBlur = () => { - clearInterval(this.refreshInterval); + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + } }; /** @@ -138,7 +148,7 @@ class WebSectionList extends React.PureComponent, StateType> { * * @param fetchedData The newly fetched data */ - onFetchSuccess = (fetchedData: ApiGenericDataType) => { + onFetchSuccess = (fetchedData: RawData) => { this.setState({ fetchedData, refreshing: false, @@ -167,7 +177,9 @@ class WebSectionList extends React.PureComponent, StateType> { if (this.lastRefresh != null) { const last = this.lastRefresh; canRefresh = new Date().getTime() - last.getTime() > MIN_REFRESH_TIME; - } else canRefresh = true; + } else { + canRefresh = true; + } if (canRefresh) { this.setState({refreshing: true}); readData(fetchUrl).then(this.onFetchSuccess).catch(this.onFetchError); @@ -189,19 +201,18 @@ class WebSectionList extends React.PureComponent, StateType> { }; getItemLayout = ( - data: T, + height: number, + data: Array> | null, index: number, - ): {length: number, offset: number, index: number} | null => { - const {itemHeight} = this.props; - if (itemHeight == null) return null; + ): {length: number; offset: number; index: number} => { return { - length: itemHeight, - offset: itemHeight * index, + length: height, + offset: height * index, index, }; }; - getRenderSectionHeader = (data: {section: {title: string}}): React.Node => { + getRenderSectionHeader = (data: {section: SectionListData}) => { const {renderSectionHeader} = this.props; const {refreshing} = this.state; if (renderSectionHeader != null) { @@ -214,7 +225,7 @@ class WebSectionList extends React.PureComponent, StateType> { return null; }; - getRenderItem = (data: {item: T}): React.Node => { + getRenderItem = (data: {item: ItemT}) => { const {renderItem} = this.props; return ( @@ -223,19 +234,23 @@ class WebSectionList extends React.PureComponent, StateType> { ); }; - onScroll = (event: SyntheticEvent) => { + onScroll = (event: NativeSyntheticEvent) => { const {onScroll} = this.props; - if (onScroll != null) onScroll(event); + if (onScroll != null) { + onScroll(event); + } }; - render(): React.Node { + render() { const {props, state} = this; - let dataset = []; + const {itemHeight} = props; + let dataset: SectionListDataType = []; if ( state.fetchedData != null || (state.fetchedData == null && !props.showError) - ) + ) { dataset = props.createDataset(state.fetchedData, state.refreshing); + } const {containerPaddingTop} = props.collapsibleStack; return ( @@ -270,7 +285,11 @@ class WebSectionList extends React.PureComponent, StateType> { /> ) } - getItemLayout={props.itemHeight != null ? this.getItemLayout : null} + getItemLayout={ + itemHeight + ? (data, index) => this.getItemLayout(itemHeight, data, index) + : undefined + } onScroll={this.onScroll} hasTab /> diff --git a/src/components/Screens/WebViewScreen.js b/src/components/Screens/WebViewScreen.tsx similarity index 84% rename from src/components/Screens/WebViewScreen.js rename to src/components/Screens/WebViewScreen.tsx index 941b223..7a2feed 100644 --- a/src/components/Screens/WebViewScreen.js +++ b/src/components/Screens/WebViewScreen.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import WebView from 'react-native-webview'; import { @@ -27,12 +25,17 @@ import { OverflowMenu, } from 'react-navigation-header-buttons'; import i18n from 'i18n-js'; -import {Animated, BackHandler, Linking} from 'react-native'; +import { + Animated, + BackHandler, + Linking, + NativeScrollEvent, + NativeSyntheticEvent, +} from 'react-native'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import {withTheme} from 'react-native-paper'; import {StackNavigationProp} from '@react-navigation/stack'; import {Collapsible} from 'react-navigation-collapsible'; -import type {CustomThemeType} from '../../managers/ThemeManager'; import withCollapsible from '../../utils/withCollapsible'; import MaterialHeaderButtons, {Item} from '../Overrides/CustomHeaderButton'; import {ERROR_TYPE} from '../../utils/WebData'; @@ -40,15 +43,15 @@ import ErrorView from './ErrorView'; import BasicLoadingScreen from './BasicLoadingScreen'; type PropsType = { - navigation: StackNavigationProp, - theme: CustomThemeType, - url: string, - collapsibleStack: Collapsible, - onMessage: (event: {nativeEvent: {data: string}}) => void, - onScroll: (event: SyntheticEvent) => void, - customJS?: string, - customPaddingFunction?: null | ((padding: number) => string), - showAdvancedControls?: boolean, + navigation: StackNavigationProp; + theme: ReactNativePaper.Theme; + url: string; + collapsibleStack: Collapsible; + onMessage: (event: {nativeEvent: {data: string}}) => void; + onScroll: (event: NativeSyntheticEvent) => void; + customJS?: string; + customPaddingFunction?: null | ((padding: number) => string); + showAdvancedControls?: boolean; }; const AnimatedWebView = Animated.createAnimatedComponent(WebView); @@ -67,8 +70,8 @@ class WebViewScreen extends React.PureComponent { canGoBack: boolean; - constructor() { - super(); + constructor(props: PropsType) { + super(props); this.webviewRef = React.createRef(); this.canGoBack = false; } @@ -115,7 +118,7 @@ class WebViewScreen extends React.PureComponent { * * @return {*} */ - getBasicButton = (): React.Node => { + getBasicButton = () => { return ( { * * @returns {*} */ - getAdvancedButtons = (): React.Node => { + getAdvancedButtons = () => { const {props} = this; return ( @@ -179,7 +182,7 @@ class WebViewScreen extends React.PureComponent { * * @return {*} */ - getRenderLoading = (): React.Node => ; + getRenderLoading = () => ; /** * Gets the javascript needed to generate a padding on top of the page @@ -201,15 +204,21 @@ class WebViewScreen extends React.PureComponent { * Callback to use when refresh button is clicked. Reloads the webview. */ onRefreshClicked = () => { - if (this.webviewRef.current != null) this.webviewRef.current.reload(); + if (this.webviewRef.current != null) { + this.webviewRef.current.reload(); + } }; onGoBackClicked = () => { - if (this.webviewRef.current != null) this.webviewRef.current.goBack(); + if (this.webviewRef.current != null) { + this.webviewRef.current.goBack(); + } }; onGoForwardClicked = () => { - if (this.webviewRef.current != null) this.webviewRef.current.goForward(); + if (this.webviewRef.current != null) { + this.webviewRef.current.goForward(); + } }; onOpenClicked = () => { @@ -217,9 +226,11 @@ class WebViewScreen extends React.PureComponent { Linking.openURL(url); }; - onScroll = (event: SyntheticEvent) => { + onScroll = (event: NativeSyntheticEvent) => { const {onScroll} = this.props; - if (onScroll) onScroll(event); + if (onScroll) { + onScroll(event); + } }; /** @@ -228,11 +239,12 @@ class WebViewScreen extends React.PureComponent { * @param script The script to inject */ injectJavaScript = (script: string) => { - if (this.webviewRef.current != null) + if (this.webviewRef.current != null) { this.webviewRef.current.injectJavaScript(script); + } }; - render(): React.Node { + render() { const {props} = this; const {containerPaddingTop, onScrollWithListener} = props.collapsibleStack; return ( @@ -243,7 +255,7 @@ class WebViewScreen extends React.PureComponent { injectedJavaScript={props.customJS} javaScriptEnabled renderLoading={this.getRenderLoading} - renderError={(): React.Node => ( + renderError={() => ( { this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop)); }} // Animations - onScroll={onScrollWithListener(this.onScroll)} + onScroll={(event) => onScrollWithListener(this.onScroll)(event)} /> ); }