diff --git a/package-lock.json b/package-lock.json index b88f93a..9f8c8b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2608,6 +2608,17 @@ "@types/react": "*" } }, + "@types/react-native-calendars": { + "version": "1.20.10", + "resolved": "https://registry.npmjs.org/@types/react-native-calendars/-/react-native-calendars-1.20.10.tgz", + "integrity": "sha512-bmWlkFa/6SNF98aM9rjKMGUOSDb15VBsfxBW5oo/iJ5tm5THf+eAGlxH72hGZFqJpr93plBs+ctkRVHQA7fx1w==", + "dev": true, + "requires": { + "@types/react": "*", + "@types/react-native": "*", + "@types/xdate": "*" + } + }, "@types/react-native-vector-icons": { "version": "6.4.6", "resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.6.tgz", @@ -2632,6 +2643,12 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, + "@types/xdate": { + "version": "0.8.31", + "resolved": "https://registry.npmjs.org/@types/xdate/-/xdate-0.8.31.tgz", + "integrity": "sha512-iZYRKKK8UZXoepNh2kwK6TPITMj/dwdv0NzNi9DFMt2foGkU7h+ncaCpGsdD2fp/CXMs9dxPAzV9uddFy7c4QA==", + "dev": true + }, "@types/yargs": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", diff --git a/package.json b/package.json index f46612a..1f2dd92 100644 --- a/package.json +++ b/package.json @@ -67,9 +67,10 @@ "@babel/runtime": "^7.11.0", "@react-native-community/eslint-config": "^1.1.0", "@types/i18n-js": "^3.0.3", - "@types/react-native-vector-icons": "^6.4.6", "@types/jest": "^25.2.3", "@types/react-native": "^0.63.2", + "@types/react-native-calendars": "^1.20.10", + "@types/react-native-vector-icons": "^6.4.6", "@types/react-test-renderer": "^16.9.2", "@typescript-eslint/eslint-plugin": "^2.27.0", "@typescript-eslint/parser": "^2.27.0", diff --git a/src/components/Intro/IconIntro.js b/src/components/Intro/IconIntro.tsx similarity index 71% rename from src/components/Intro/IconIntro.js rename to src/components/Intro/IconIntro.tsx index 34c9c7b..6017024 100644 --- a/src/components/Intro/IconIntro.js +++ b/src/components/Intro/IconIntro.tsx @@ -17,15 +17,13 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {StyleSheet, View} from 'react-native'; import * as Animatable from 'react-native-animatable'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; type PropsType = { - icon: string, + icon: string; }; const styles = StyleSheet.create({ @@ -37,24 +35,14 @@ const styles = StyleSheet.create({ }, }); -class IntroIcon extends React.Component { - shouldComponentUpdate(): boolean { - return false; - } - - render(): React.Node { - const {icon} = this.props; - return ( - - - - - - ); - } +function IntroIcon(props: PropsType) { + return ( + + + + + + ); } export default IntroIcon; diff --git a/src/components/Intro/MascotIntroEnd.js b/src/components/Intro/MascotIntroEnd.tsx similarity index 62% rename from src/components/Intro/MascotIntroEnd.js rename to src/components/Intro/MascotIntroEnd.tsx index 75075a5..ffffa0b 100644 --- a/src/components/Intro/MascotIntroEnd.js +++ b/src/components/Intro/MascotIntroEnd.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {StyleSheet, View} from 'react-native'; import Mascot, {MASCOT_STYLE} from '../Mascot/Mascot'; @@ -32,34 +30,28 @@ const styles = StyleSheet.create({ }, }); -class MascotIntroEnd extends React.Component { - shouldComponentUpdate(): boolean { - return false; - } - - render(): React.Node { - return ( - - - - ); - } +function MascotIntroEnd() { + return ( + + + + ); } export default MascotIntroEnd; diff --git a/src/components/Intro/MascotIntroWelcome.js b/src/components/Intro/MascotIntroWelcome.tsx similarity index 50% rename from src/components/Intro/MascotIntroWelcome.js rename to src/components/Intro/MascotIntroWelcome.tsx index dba0cdd..4a73719 100644 --- a/src/components/Intro/MascotIntroWelcome.js +++ b/src/components/Intro/MascotIntroWelcome.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {StyleSheet, View} from 'react-native'; import * as Animatable from 'react-native-animatable'; @@ -34,62 +32,56 @@ const styles = StyleSheet.create({ }, }); -class MascotIntroWelcome extends React.Component { - shouldComponentUpdate(): boolean { - return false; - } - - render(): React.Node { - return ( - - + + + PABLO + + + - - PABLO - - - - - - ); - } + + + ); } export default MascotIntroWelcome; diff --git a/src/components/Overrides/CustomAgenda.js b/src/components/Overrides/CustomAgenda.js deleted file mode 100644 index 9ee731d..0000000 --- a/src/components/Overrides/CustomAgenda.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 . - */ - -// @flow - -import * as React from 'react'; -import {View} from 'react-native'; -import {withTheme} from 'react-native-paper'; -import {Agenda} from 'react-native-calendars'; -import type {CustomThemeType} from '../../managers/ThemeManager'; - -type PropsType = { - theme: CustomThemeType, - onRef: (ref: Agenda) => void, -}; - -/** - * Abstraction layer for Agenda component, using custom configuration - */ -class CustomAgenda extends React.Component { - getAgenda(): React.Node { - const {props} = this; - return ( - - ); - } - - render(): React.Node { - const {props} = this; - // Completely recreate the component on theme change to force theme reload - if (props.theme.dark) - return {this.getAgenda()}; - return this.getAgenda(); - } -} - -export default withTheme(CustomAgenda); diff --git a/src/components/Overrides/CustomAgenda.tsx b/src/components/Overrides/CustomAgenda.tsx new file mode 100644 index 0000000..47dd166 --- /dev/null +++ b/src/components/Overrides/CustomAgenda.tsx @@ -0,0 +1,75 @@ +/* + * 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 . + */ + +import * as React from 'react'; +import {View} from 'react-native'; +import {useTheme} from 'react-native-paper'; +import {Agenda, AgendaProps} from 'react-native-calendars'; + +type PropsType = { + onRef: (ref: Agenda) => void; +} & AgendaProps; + +/** + * Abstraction layer for Agenda component, using custom configuration + */ +function CustomAgenda(props: PropsType) { + const theme = useTheme(); + function getAgenda() { + return ( + + ); + } + + // Completely recreate the component on theme change to force theme reload + if (theme.dark) { + return {getAgenda()}; + } + return getAgenda(); +} + +export default CustomAgenda; diff --git a/src/components/Overrides/CustomHTML.js b/src/components/Overrides/CustomHTML.js deleted file mode 100644 index 2721b40..0000000 --- a/src/components/Overrides/CustomHTML.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 . - */ - -/* eslint-disable flowtype/require-parameter-type */ -// @flow - -import * as React from 'react'; -import {Text, withTheme} from 'react-native-paper'; -import HTML from 'react-native-render-html'; -import {Linking} from 'react-native'; -import type {CustomThemeType} from '../../managers/ThemeManager'; - -type PropsType = { - theme: CustomThemeType, - html: string, -}; - -/** - * Abstraction layer for Agenda component, using custom configuration - */ -class CustomHTML extends React.Component { - openWebLink = (event: {...}, link: string) => { - Linking.openURL(link); - }; - - getBasicText = ( - htmlAttribs, - children, - convertedCSSStyles, - passProps, - ): React.Node => { - // eslint-disable-next-line react/jsx-props-no-spreading - return {children}; - }; - - getListBullet = (): React.Node => { - return - ; - }; - - render(): React.Node { - const {props} = this; - // Surround description with p to allow text styling if the description is not html - return ( - ${props.html}

`} - renderers={{ - p: this.getBasicText, - li: this.getBasicText, - }} - listsPrefixesRenderers={{ - ul: this.getListBullet, - }} - ignoredTags={['img']} - ignoredStyles={['color', 'background-color']} - onLinkPress={this.openWebLink} - /> - ); - } -} - -export default withTheme(CustomHTML); diff --git a/src/components/Overrides/CustomHTML.tsx b/src/components/Overrides/CustomHTML.tsx new file mode 100644 index 0000000..95edf84 --- /dev/null +++ b/src/components/Overrides/CustomHTML.tsx @@ -0,0 +1,68 @@ +/* + * 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 . + */ + +import * as React from 'react'; +import {Text} from 'react-native-paper'; +import HTML from 'react-native-render-html'; +import {GestureResponderEvent, Linking} from 'react-native'; + +type PropsType = { + html: string; +}; + +/** + * Abstraction layer for Agenda component, using custom configuration + */ +function CustomHTML(props: PropsType) { + const openWebLink = (event: GestureResponderEvent, link: string) => { + Linking.openURL(link); + }; + + const getBasicText = ( + htmlAttribs: any, + children: any, + convertedCSSStyles: any, + passProps: any, + ) => { + return {children}; + }; + + const getListBullet = () => { + return - ; + }; + + // Surround description with p to allow text styling if the description is not html + return ( + ${props.html}

`} + renderers={{ + p: getBasicText, + li: getBasicText, + }} + listsPrefixesRenderers={{ + ul: getListBullet, + }} + ignoredTags={['img']} + ignoredStyles={['color', 'background-color']} + onLinkPress={openWebLink} + /> + ); +} + +export default CustomHTML; diff --git a/src/components/Overrides/CustomHeaderButton.js b/src/components/Overrides/CustomHeaderButton.tsx similarity index 60% rename from src/components/Overrides/CustomHeaderButton.js rename to src/components/Overrides/CustomHeaderButton.tsx index be36c83..ded22b4 100644 --- a/src/components/Overrides/CustomHeaderButton.js +++ b/src/components/Overrides/CustomHeaderButton.tsx @@ -17,39 +17,31 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; -import {HeaderButton, HeaderButtons} from 'react-navigation-header-buttons'; -import {withTheme} from 'react-native-paper'; -import type {CustomThemeType} from '../../managers/ThemeManager'; +import { + HeaderButton, + HeaderButtonProps, + HeaderButtons, + HeaderButtonsProps, +} from 'react-navigation-header-buttons'; +import {useTheme} from 'react-native-paper'; -const MaterialHeaderButton = (props: { - theme: CustomThemeType, - color: string, -}): React.Node => { - const {color, theme} = props; +const MaterialHeaderButton = (props: HeaderButtonProps) => { + const theme = useTheme(); return ( - // $FlowFixMe ); }; -const MaterialHeaderButtons = (props: {...}): React.Node => { +const MaterialHeaderButtons = (props: HeaderButtonsProps) => { return ( - // $FlowFixMe - + ); }; diff --git a/src/components/Overrides/CustomIntroSlider.js b/src/components/Overrides/CustomIntroSlider.tsx similarity index 85% rename from src/components/Overrides/CustomIntroSlider.js rename to src/components/Overrides/CustomIntroSlider.tsx index 1d669e0..d773d42 100644 --- a/src/components/Overrides/CustomIntroSlider.js +++ b/src/components/Overrides/CustomIntroSlider.tsx @@ -17,10 +17,14 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; -import {Platform, StatusBar, StyleSheet, View} from 'react-native'; +import { + ListRenderItemInfo, + Platform, + StatusBar, + StyleSheet, + View, +} from 'react-native'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import i18n from 'i18n-js'; import AppIntroSlider from 'react-native-app-intro-slider'; @@ -35,26 +39,27 @@ import IntroIcon from '../Intro/IconIntro'; import MascotIntroEnd from '../Intro/MascotIntroEnd'; type PropsType = { - onDone: () => void, - isUpdate: boolean, - isAprilFools: boolean, + onDone: () => void; + isUpdate: boolean; + isAprilFools: boolean; }; type StateType = { - currentSlide: number, + currentSlide: number; }; export type IntroSlideType = { - key: string, - title: string, - text: string, - view: () => React.Node, - mascotStyle?: number, - colors: [string, string], + key: string; + title: string; + text: string; + view: () => React.ReactNode; + mascotStyle?: number; + colors: [string, string]; }; const styles = StyleSheet.create({ mainContent: { + flex: 1, paddingBottom: 100, }, text: { @@ -83,7 +88,7 @@ const styles = StyleSheet.create({ */ export default class CustomIntroSlider extends React.Component< PropsType, - StateType, + StateType > { sliderRef: {current: null | AppIntroSlider}; @@ -98,8 +103,9 @@ export default class CustomIntroSlider extends React.Component< /** * Generates intro slides */ - constructor() { - super(); + constructor(props: PropsType) { + super(props); + this.currentSlides = []; this.state = { currentSlide: 0, }; @@ -109,14 +115,14 @@ export default class CustomIntroSlider extends React.Component< key: '0', // Mascot title: i18n.t('intro.slideMain.title'), text: i18n.t('intro.slideMain.text'), - view: (): React.Node => , + view: () => , colors: ['#be1522', '#57080e'], }, { key: '1', title: i18n.t('intro.slidePlanex.title'), text: i18n.t('intro.slidePlanex.text'), - view: (): React.Node => , + view: () => , mascotStyle: MASCOT_STYLE.INTELLO, colors: ['#be1522', '#57080e'], }, @@ -124,7 +130,7 @@ export default class CustomIntroSlider extends React.Component< key: '2', title: i18n.t('intro.slideEvents.title'), text: i18n.t('intro.slideEvents.text'), - view: (): React.Node => , + view: () => , mascotStyle: MASCOT_STYLE.HAPPY, colors: ['#be1522', '#57080e'], }, @@ -132,7 +138,7 @@ export default class CustomIntroSlider extends React.Component< key: '3', title: i18n.t('intro.slideServices.title'), text: i18n.t('intro.slideServices.text'), - view: (): React.Node => , + view: () => , mascotStyle: MASCOT_STYLE.CUTE, colors: ['#be1522', '#57080e'], }, @@ -140,7 +146,7 @@ export default class CustomIntroSlider extends React.Component< key: '4', title: i18n.t('intro.slideDone.title'), text: i18n.t('intro.slideDone.text'), - view: (): React.Node => , + view: () => , colors: ['#9c165b', '#3e042b'], }, ]; @@ -152,7 +158,7 @@ export default class CustomIntroSlider extends React.Component< key: '1', title: i18n.t('intro.aprilFoolsSlide.title'), text: i18n.t('intro.aprilFoolsSlide.text'), - view: (): React.Node => , + view: () => , mascotStyle: MASCOT_STYLE.NORMAL, colors: ['#e01928', '#be1522'], }, @@ -162,21 +168,21 @@ export default class CustomIntroSlider extends React.Component< /** * Render item to be used for the intro introSlides * - * @param item The item to be displayed - * @param dimensions Dimensions of the item + * @param data */ - getIntroRenderItem = ({ - item, - dimensions, - }: { - item: IntroSlideType, - dimensions: {width: number, height: number}, - }): React.Node => { + getIntroRenderItem = ( + data: + | (ListRenderItemInfo & { + dimensions: {width: number; height: number}; + }) + | ListRenderItemInfo, + ) => { + const item = data.item; const {state} = this; const index = parseInt(item.key, 10); return ( @@ -254,7 +260,9 @@ export default class CustomIntroSlider extends React.Component< }; static setStatusBarColor(color: string) { - if (Platform.OS === 'android') StatusBar.setBackgroundColor(color, true); + if (Platform.OS === 'android') { + StatusBar.setBackgroundColor(color, true); + } } onSlideChange = (index: number) => { @@ -266,8 +274,9 @@ export default class CustomIntroSlider extends React.Component< CustomIntroSlider.setStatusBarColor( this.currentSlides[this.currentSlides.length - 1].colors[0], ); - if (this.sliderRef.current != null) + if (this.sliderRef.current != null) { this.sliderRef.current.goToSlide(this.currentSlides.length - 1); + } }; onDone = () => { @@ -278,7 +287,7 @@ export default class CustomIntroSlider extends React.Component< props.onDone(); }; - getRenderNextButton = (): React.Node => { + getRenderNextButton = () => { return ( { + getRenderDoneButton = () => { return ( . */ -// @flow - import * as React from 'react'; -import {withTheme} from 'react-native-paper'; +import {useTheme} from 'react-native-paper'; import {Modalize} from 'react-native-modalize'; import {View} from 'react-native-animatable'; import CustomTabBar from '../Tabbar/CustomTabBar'; -import type {CustomThemeType} from '../../managers/ThemeManager'; /** * Abstraction layer for Modalize component, using custom configuration @@ -33,11 +30,11 @@ import type {CustomThemeType} from '../../managers/ThemeManager'; * @return {*} */ function CustomModal(props: { - theme: CustomThemeType, - onRef: (re: Modalize) => void, - children?: React.Node, -}): React.Node { - const {theme, onRef, children} = props; + onRef: (re: Modalize) => void; + children?: React.ReactNode; +}) { + const theme = useTheme(); + const {onRef, children} = props; return ( . - */ - -// @flow - -import * as React from 'react'; -import {Text, withTheme} from 'react-native-paper'; -import {View} from 'react-native-animatable'; -import Slider, {SliderProps} from '@react-native-community/slider'; -import type {CustomThemeType} from '../../managers/ThemeManager'; - -type PropsType = { - theme: CustomThemeType, - valueSuffix?: string, - ...SliderProps, -}; - -type StateType = { - currentValue: number, -}; - -/** - * Abstraction layer for Modalize component, using custom configuration - * - * @param props Props to pass to the element. Must specify an onRef prop to get an Modalize ref. - * @return {*} - */ -class CustomSlider extends React.Component { - static defaultProps = { - valueSuffix: '', - }; - - constructor(props: PropsType) { - super(props); - this.state = { - currentValue: props.value, - }; - } - - onValueChange = (value: number) => { - const {props} = this; - this.setState({currentValue: value}); - if (props.onValueChange != null) props.onValueChange(value); - }; - - render(): React.Node { - const {props, state} = this; - return ( - - - {state.currentValue}min - - - - ); - } -} - -export default withTheme(CustomSlider); diff --git a/src/components/Overrides/CustomSlider.tsx b/src/components/Overrides/CustomSlider.tsx new file mode 100644 index 0000000..9bb92ab --- /dev/null +++ b/src/components/Overrides/CustomSlider.tsx @@ -0,0 +1,61 @@ +/* + * 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 . + */ + +import * as React from 'react'; +import {Text} from 'react-native-paper'; +import {View} from 'react-native-animatable'; +import Slider, {SliderProps} from '@react-native-community/slider'; +import {useState} from 'react'; + +type PropsType = { + valueSuffix?: string; +} & SliderProps; + +/** + * Abstraction layer for Modalize component, using custom configuration + * + * @param props Props to pass to the element. Must specify an onRef prop to get an Modalize ref. + * @return {*} + */ +function CustomSlider(props: PropsType) { + const [currentValue, setCurrentValue] = useState(props.value); + + const onValueChange = (value: number) => { + setCurrentValue(value); + if (props.onValueChange) { + props.onValueChange(value); + } + }; + + return ( + + + {currentValue}min + + + + ); +} + +export default CustomSlider;