Update home screens to use TypeScript

This commit is contained in:
Arnaud Vergnet 2020-09-22 22:52:35 +02:00
parent 38afbf02a3
commit c198a40148
4 changed files with 102 additions and 128 deletions

View file

@ -23,9 +23,9 @@ import {Dimensions, Image, View} from 'react-native';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
type PropsType = { type PropsType = {
image: string | null; image?: string | number;
onPress: () => void | null; onPress?: () => void;
badgeCount: number | null; badgeCount?: number;
}; };
/** /**
@ -50,7 +50,7 @@ function SmallDashboardItem(props: PropsType) {
}}> }}>
{image ? ( {image ? (
<Image <Image
source={{uri: image}} source={typeof image === 'string' ? {uri: image} : image}
style={{ style={{
width: '80%', width: '80%',
height: '80%', height: '80%',

View file

@ -17,11 +17,9 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {Linking, Image} from 'react-native'; import {Linking, Image} from 'react-native';
import {Card, Text, withTheme} from 'react-native-paper'; import {Card, Text} from 'react-native-paper';
import Autolink from 'react-native-autolink'; import Autolink from 'react-native-autolink';
import {StackNavigationProp} from '@react-navigation/stack'; import {StackNavigationProp} from '@react-navigation/stack';
import MaterialHeaderButtons, { import MaterialHeaderButtons, {
@ -31,12 +29,14 @@ import CustomTabBar from '../../components/Tabbar/CustomTabBar';
import type {FeedItemType} from './HomeScreen'; import type {FeedItemType} from './HomeScreen';
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
import ImageGalleryButton from '../../components/Media/ImageGalleryButton'; import ImageGalleryButton from '../../components/Media/ImageGalleryButton';
import NewsSourcesConstants from '../../constants/NewsSourcesConstants'; import NewsSourcesConstants, {
AvailablePages,
} from '../../constants/NewsSourcesConstants';
import type {NewsSourceType} from '../../constants/NewsSourcesConstants'; import type {NewsSourceType} from '../../constants/NewsSourcesConstants';
type PropsType = { type PropsType = {
navigation: StackNavigationProp, navigation: StackNavigationProp<any>;
route: {params: {data: FeedItemType, date: string}}, route: {params: {data: FeedItemType; date: string}};
}; };
/** /**
@ -72,7 +72,7 @@ class FeedItemScreen extends React.Component<PropsType> {
* *
* @returns {*} * @returns {*}
*/ */
getHeaderButton = (): React.Node => { getHeaderButton = () => {
return ( return (
<MaterialHeaderButtons> <MaterialHeaderButtons>
<Item <Item
@ -85,20 +85,16 @@ class FeedItemScreen extends React.Component<PropsType> {
); );
}; };
render(): React.Node { render() {
const {navigation} = this.props;
const hasImage =
this.displayData.image !== '' && this.displayData.image != null;
const pageSource: NewsSourceType = const pageSource: NewsSourceType =
NewsSourcesConstants[this.displayData.page_id]; NewsSourcesConstants[this.displayData.page_id as AvailablePages];
return ( return (
<CollapsibleScrollView style={{margin: 5}} hasTab> <CollapsibleScrollView style={{margin: 5}} hasTab>
<Card.Title <Card.Title
title={pageSource.name} title={pageSource.name}
subtitle={this.date} subtitle={this.date}
left={(): React.Node => ( left={() => (
<Image <Image
size={48}
source={pageSource.icon} source={pageSource.icon}
style={{ style={{
width: 48, width: 48,
@ -107,9 +103,8 @@ class FeedItemScreen extends React.Component<PropsType> {
/> />
)} )}
/> />
{hasImage ? ( {this.displayData.image ? (
<ImageGalleryButton <ImageGalleryButton
navigation={navigation}
images={[{url: this.displayData.image}]} images={[{url: this.displayData.image}]}
style={{ style={{
width: 250, width: 250,
@ -124,6 +119,7 @@ class FeedItemScreen extends React.Component<PropsType> {
<Autolink <Autolink
text={this.displayData.message} text={this.displayData.message}
hashtag="facebook" hashtag="facebook"
// @ts-ignore
component={Text} component={Text}
/> />
) : null} ) : null}
@ -133,4 +129,4 @@ class FeedItemScreen extends React.Component<PropsType> {
} }
} }
export default withTheme(FeedItemScreen); export default FeedItemScreen;

View file

@ -17,10 +17,8 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {FlatList} from 'react-native'; import {FlatList, NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {ActivityIndicator, Headline, withTheme} from 'react-native-paper'; import {ActivityIndicator, Headline, withTheme} from 'react-native-paper';
import {CommonActions} from '@react-navigation/native'; import {CommonActions} from '@react-navigation/native';
@ -38,7 +36,6 @@ import MaterialHeaderButtons, {
Item, Item,
} from '../../components/Overrides/CustomHeaderButton'; } from '../../components/Overrides/CustomHeaderButton';
import AnimatedFAB from '../../components/Animations/AnimatedFAB'; import AnimatedFAB from '../../components/Animations/AnimatedFAB';
import type {CustomThemeType} from '../../managers/ThemeManager';
import ConnectionManager from '../../managers/ConnectionManager'; import ConnectionManager from '../../managers/ConnectionManager';
import LogoutDialog from '../../components/Amicale/LogoutDialog'; import LogoutDialog from '../../components/Amicale/LogoutDialog';
import AsyncStorageManager from '../../managers/AsyncStorageManager'; import AsyncStorageManager from '../../managers/AsyncStorageManager';
@ -59,40 +56,40 @@ const SECTIONS_ID = ['dashboard', 'news_feed'];
const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
export type FeedItemType = { export type FeedItemType = {
id: string, id: string;
message: string, message: string;
url: string, url: string;
image: string | null, image: string | null;
video: string | null, video: string | null;
link: string | null, link: string | null;
time: number, time: number;
page_id: string, page_id: string;
}; };
export type FullDashboardType = { export type FullDashboardType = {
today_menu: Array<{[key: string]: {...}}>, today_menu: Array<{[key: string]: object}>;
proximo_articles: number, proximo_articles: number;
available_dryers: number, available_dryers: number;
available_washers: number, available_washers: number;
today_events: Array<PlanningEventType>, today_events: Array<PlanningEventType>;
available_tutorials: number, available_tutorials: number;
}; };
type RawNewsFeedType = {[key: string]: Array<FeedItemType>}; type RawNewsFeedType = {[key: string]: Array<FeedItemType>};
type RawDashboardType = { type RawDashboardType = {
news_feed: RawNewsFeedType, news_feed: RawNewsFeedType;
dashboard: FullDashboardType, dashboard: FullDashboardType;
}; };
type PropsType = { type PropsType = {
navigation: StackNavigationProp, navigation: StackNavigationProp<any>;
route: {params: {nextScreen: string, data: {...}}}, route: {params: {nextScreen: string; data: object}};
theme: CustomThemeType, theme: ReactNativePaper.Theme;
}; };
type StateType = { type StateType = {
dialogVisible: boolean, dialogVisible: boolean;
}; };
/** /**
@ -103,10 +100,12 @@ class HomeScreen extends React.Component<PropsType, StateType> {
b.time - a.time; b.time - a.time;
static generateNewsFeed(rawFeed: RawNewsFeedType): Array<FeedItemType> { static generateNewsFeed(rawFeed: RawNewsFeedType): Array<FeedItemType> {
const finalFeed = []; const finalFeed: Array<FeedItemType> = [];
Object.keys(rawFeed).forEach((key: string) => { Object.keys(rawFeed).forEach((key: string) => {
const category: Array<FeedItemType> | null = rawFeed[key]; const category: Array<FeedItemType> | null = rawFeed[key];
if (category != null && category.length > 0) finalFeed.push(...category); if (category != null && category.length > 0) {
finalFeed.push(...category);
}
}); });
finalFeed.sort(HomeScreen.sortFeedTime); finalFeed.sort(HomeScreen.sortFeedTime);
return finalFeed; return finalFeed;
@ -164,7 +163,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
* *
* @returns {*} * @returns {*}
*/ */
getHeaderButton = (): React.Node => { getHeaderButton = () => {
const {props} = this; const {props} = this;
let onPressLog = (): void => let onPressLog = (): void =>
props.navigation.navigate('login', {nextScreen: 'profile'}); props.navigation.navigate('login', {nextScreen: 'profile'});
@ -201,7 +200,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
* @param content * @param content
* @return {*} * @return {*}
*/ */
getDashboardEvent(content: Array<PlanningEventType>): React.Node { getDashboardEvent(content: Array<PlanningEventType>) {
const futureEvents = getFutureEvents(content); const futureEvents = getFutureEvents(content);
const displayEvent = getDisplayEvent(futureEvents); const displayEvent = getDisplayEvent(futureEvents);
// const clickPreviewAction = () => // const clickPreviewAction = () =>
@ -221,30 +220,14 @@ class HomeScreen extends React.Component<PropsType, StateType> {
); );
} }
/**
* Gets a dashboard item with action buttons
*
* @returns {*}
*/
getDashboardActions(): React.Node {
const {props} = this;
return (
<ActionsDashBoardItem
navigation={props.navigation}
isLoggedIn={this.isLoggedIn}
/>
);
}
/** /**
* Gets a dashboard item with a row of shortcut buttons. * Gets a dashboard item with a row of shortcut buttons.
* *
* @param content * @param content
* @return {*} * @return {*}
*/ */
getDashboardRow(content: Array<ServiceItemType | null>): React.Node { getDashboardRow(content: Array<ServiceItemType | null>) {
return ( return (
// $FlowFixMe
<FlatList <FlatList
data={content} data={content}
renderItem={this.getDashboardRowRenderItem} renderItem={this.getDashboardRowRenderItem}
@ -265,12 +248,8 @@ class HomeScreen extends React.Component<PropsType, StateType> {
* @param item * @param item
* @returns {*} * @returns {*}
*/ */
getDashboardRowRenderItem = ({ getDashboardRowRenderItem = ({item}: {item: ServiceItemType | null}) => {
item, if (item != null) {
}: {
item: ServiceItemType | null,
}): React.Node => {
if (item != null)
return ( return (
<SmallDashboardItem <SmallDashboardItem
image={item.image} image={item.image}
@ -278,11 +257,12 @@ class HomeScreen extends React.Component<PropsType, StateType> {
badgeCount={ badgeCount={
this.currentDashboard != null && item.badgeFunction != null this.currentDashboard != null && item.badgeFunction != null
? item.badgeFunction(this.currentDashboard) ? item.badgeFunction(this.currentDashboard)
: null : undefined
} }
/> />
); );
return <SmallDashboardItem image={null} onPress={null} badgeCount={null} />; }
return <SmallDashboardItem />;
}; };
/** /**
@ -291,15 +271,8 @@ class HomeScreen extends React.Component<PropsType, StateType> {
* @param item The feed item to display * @param item The feed item to display
* @return {*} * @return {*}
*/ */
getFeedItem(item: FeedItemType): React.Node { getFeedItem(item: FeedItemType) {
const {props} = this; return <FeedItem item={item} height={FEED_ITEM_HEIGHT} />;
return (
<FeedItem
navigation={props.navigation}
item={item}
height={FEED_ITEM_HEIGHT}
/>
);
} }
/** /**
@ -309,20 +282,19 @@ class HomeScreen extends React.Component<PropsType, StateType> {
* @param section The current section * @param section The current section
* @return {*} * @return {*}
*/ */
getRenderItem = ({item}: {item: FeedItemType}): React.Node => getRenderItem = ({item}: {item: FeedItemType}) => this.getFeedItem(item);
this.getFeedItem(item);
getRenderSectionHeader = ( getRenderSectionHeader = (
data: { data: {
section: { section: {
data: Array<{...}>, data: Array<object>;
title: string, title: string;
}, };
}, },
isLoading: boolean, isLoading: boolean,
): React.Node => { ) => {
const {props} = this; const {props} = this;
if (data.section.data.length > 0) if (data.section.data.length > 0) {
return ( return (
<Headline <Headline
style={{ style={{
@ -333,6 +305,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
{data.section.title} {data.section.title}
</Headline> </Headline>
); );
}
return ( return (
<View> <View>
<Headline <Headline
@ -367,12 +340,14 @@ class HomeScreen extends React.Component<PropsType, StateType> {
); );
}; };
getListHeader = (fetchedData: RawDashboardType): React.Node => { getListHeader = (fetchedData: RawDashboardType) => {
let dashboard = null; let dashboard = null;
if (fetchedData != null) dashboard = fetchedData.dashboard; if (fetchedData != null) {
dashboard = fetchedData.dashboard;
}
return ( return (
<Animatable.View animation="fadeInDown" duration={500} useNativeDriver> <Animatable.View animation="fadeInDown" duration={500} useNativeDriver>
{this.getDashboardActions()} <ActionsDashBoardItem />
{this.getDashboardRow(this.dashboardManager.getCurrentDashboard())} {this.getDashboardRow(this.dashboardManager.getCurrentDashboard())}
{this.getDashboardEvent( {this.getDashboardEvent(
dashboard == null ? [] : dashboard.today_events, dashboard == null ? [] : dashboard.today_events,
@ -418,20 +393,22 @@ class HomeScreen extends React.Component<PropsType, StateType> {
fetchedData: RawDashboardType | null, fetchedData: RawDashboardType | null,
isLoading: boolean, isLoading: boolean,
): Array<{ ): Array<{
title: string, title: string;
data: [] | Array<FeedItemType>, data: [] | Array<FeedItemType>;
id: string, id: string;
}> => { }> => {
// fetchedData = DATA; // fetchedData = DATA;
if (fetchedData != null) { if (fetchedData != null) {
if (fetchedData.news_feed != null) if (fetchedData.news_feed != null) {
this.currentNewFeed = HomeScreen.generateNewsFeed( this.currentNewFeed = HomeScreen.generateNewsFeed(
fetchedData.news_feed, fetchedData.news_feed,
); );
if (fetchedData.dashboard != null) }
if (fetchedData.dashboard != null) {
this.currentDashboard = fetchedData.dashboard; this.currentDashboard = fetchedData.dashboard;
}
} }
if (this.currentNewFeed.length > 0) if (this.currentNewFeed.length > 0) {
return [ return [
{ {
title: i18n.t('screens.home.feedTitle'), title: i18n.t('screens.home.feedTitle'),
@ -439,6 +416,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
id: SECTIONS_ID[1], id: SECTIONS_ID[1],
}, },
]; ];
}
return [ return [
{ {
title: isLoading title: isLoading
@ -455,8 +433,10 @@ class HomeScreen extends React.Component<PropsType, StateType> {
props.navigation.navigate('planning'); props.navigation.navigate('planning');
}; };
onScroll = (event: SyntheticEvent<EventTarget>) => { onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
if (this.fabRef.current != null) this.fabRef.current.onScroll(event); if (this.fabRef.current) {
this.fabRef.current.onScroll(event);
}
}; };
/** /**
@ -470,7 +450,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
}); });
}; };
render(): React.Node { render() {
const {props, state} = this; const {props, state} = this;
return ( return (
<View style={{flex: 1}}> <View style={{flex: 1}}>
@ -521,7 +501,6 @@ class HomeScreen extends React.Component<PropsType, StateType> {
onPress={this.openScanner} onPress={this.openScanner}
/> />
<LogoutDialog <LogoutDialog
navigation={props.navigation}
visible={state.dialogVisible} visible={state.dialogVisible}
onDismiss={this.hideDisconnectDialog} onDismiss={this.hideDisconnectDialog}
/> />

View file

@ -17,11 +17,9 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {Linking, Platform, StyleSheet, View} from 'react-native'; import {Linking, Platform, StyleSheet, View} from 'react-native';
import {Button, Text, withTheme} from 'react-native-paper'; import {Button, Text} from 'react-native-paper';
import {RNCamera} from 'react-native-camera'; import {RNCamera} from 'react-native-camera';
import {BarcodeMask} from '@nartc/react-native-barcode-mask'; import {BarcodeMask} from '@nartc/react-native-barcode-mask';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
@ -34,11 +32,11 @@ import {MASCOT_STYLE} from '../../components/Mascot/Mascot';
import MascotPopup from '../../components/Mascot/MascotPopup'; import MascotPopup from '../../components/Mascot/MascotPopup';
type StateType = { type StateType = {
hasPermission: boolean, hasPermission: boolean;
scanned: boolean, scanned: boolean;
dialogVisible: boolean, dialogVisible: boolean;
mascotDialogVisible: boolean, mascotDialogVisible: boolean;
loading: boolean, loading: boolean;
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -54,9 +52,11 @@ const styles = StyleSheet.create({
}, },
}); });
class ScannerScreen extends React.Component<null, StateType> { type PermissionResults = 'unavailable' | 'denied' | 'blocked' | 'granted';
class ScannerScreen extends React.Component<{}, StateType> {
constructor() { constructor() {
super(); super({});
this.state = { this.state = {
hasPermission: false, hasPermission: false,
scanned: false, scanned: false,
@ -75,7 +75,7 @@ class ScannerScreen extends React.Component<null, StateType> {
* *
* @returns {*} * @returns {*}
*/ */
getPermissionScreen(): React.Node { getPermissionScreen() {
return ( return (
<View style={{marginLeft: 10, marginRight: 10}}> <View style={{marginLeft: 10, marginRight: 10}}>
<Text>{i18n.t('screens.scanner.permissions.error')}</Text> <Text>{i18n.t('screens.scanner.permissions.error')}</Text>
@ -101,15 +101,13 @@ class ScannerScreen extends React.Component<null, StateType> {
* *
* @returns {*} * @returns {*}
*/ */
getScanner(): React.Node { getScanner() {
const {state} = this; const {state} = this;
return ( return (
<RNCamera <RNCamera
onBarCodeRead={state.scanned ? null : this.onCodeScanned} onBarCodeRead={state.scanned ? undefined : this.onCodeScanned}
type={RNCamera.Constants.Type.back} type={RNCamera.Constants.Type.back}
barCodeScannerSettings={{ barCodeTypes={['qr']}
barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
}}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
captureAudio={false}> captureAudio={false}>
<BarcodeMask <BarcodeMask
@ -128,9 +126,11 @@ class ScannerScreen extends React.Component<null, StateType> {
* Requests permission to use the camera * Requests permission to use the camera
*/ */
requestPermissions = () => { requestPermissions = () => {
if (Platform.OS === 'android') if (Platform.OS === 'android') {
request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus); request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus);
else request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus); } else {
request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus);
}
}; };
/** /**
@ -138,7 +138,7 @@ class ScannerScreen extends React.Component<null, StateType> {
* *
* @param result * @param result
*/ */
updatePermissionStatus = (result: RESULTS) => { updatePermissionStatus = (result: PermissionResults) => {
this.setState({ this.setState({
hasPermission: result === RESULTS.GRANTED, hasPermission: result === RESULTS.GRANTED,
}); });
@ -147,7 +147,6 @@ class ScannerScreen extends React.Component<null, StateType> {
/** /**
* Shows a dialog indicating the user the scanned code was invalid * Shows a dialog indicating the user the scanned code was invalid
*/ */
// eslint-disable-next-line react/sort-comp
showErrorDialog() { showErrorDialog() {
this.setState({ this.setState({
dialogVisible: true, dialogVisible: true,
@ -199,14 +198,15 @@ class ScannerScreen extends React.Component<null, StateType> {
* @param data The scanned value * @param data The scanned value
*/ */
onCodeScanned = ({data}: {data: string}) => { onCodeScanned = ({data}: {data: string}) => {
if (!URLHandler.isUrlValid(data)) this.showErrorDialog(); if (!URLHandler.isUrlValid(data)) {
else { this.showErrorDialog();
} else {
this.showOpeningDialog(); this.showOpeningDialog();
Linking.openURL(data); Linking.openURL(data);
} }
}; };
render(): React.Node { render() {
const {state} = this; const {state} = this;
return ( return (
<View <View
@ -228,7 +228,6 @@ class ScannerScreen extends React.Component<null, StateType> {
message={i18n.t('screens.scanner.mascotDialog.message')} message={i18n.t('screens.scanner.mascotDialog.message')}
icon="camera-iris" icon="camera-iris"
buttons={{ buttons={{
action: null,
cancel: { cancel: {
message: i18n.t('screens.scanner.mascotDialog.button'), message: i18n.t('screens.scanner.mascotDialog.button'),
icon: 'check', icon: 'check',
@ -253,4 +252,4 @@ class ScannerScreen extends React.Component<null, StateType> {
} }
} }
export default withTheme(ScannerScreen); export default ScannerScreen;