diff --git a/src/components/Lists/CardList/CardList.js b/src/components/Lists/CardList/CardList.tsx similarity index 84% rename from src/components/Lists/CardList/CardList.js rename to src/components/Lists/CardList/CardList.tsx index 985a49e..67eda6a 100644 --- a/src/components/Lists/CardList/CardList.js +++ b/src/components/Lists/CardList/CardList.tsx @@ -17,19 +17,16 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; -import {Animated, Dimensions} from 'react-native'; -import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet'; +import {Animated, Dimensions, ViewStyle} from 'react-native'; import ImageListItem from './ImageListItem'; import CardListItem from './CardListItem'; import type {ServiceItemType} from '../../../managers/ServicesManager'; type PropsType = { - dataset: Array, - isHorizontal?: boolean, - contentContainerStyle?: ViewStyle | null, + dataset: Array; + isHorizontal?: boolean; + contentContainerStyle?: ViewStyle; }; export default class CardList extends React.Component { @@ -45,12 +42,12 @@ export default class CardList extends React.Component { constructor(props: PropsType) { super(props); this.windowWidth = Dimensions.get('window').width; - this.horizontalItemSize = this.windowWidth / 4; // So that we can fit 3 items and a part of the 4th => user knows he can scroll + this.horizontalItemSize = this.windowWidth / 4; // So that we can fit 3 items, and a part of the 4th => user knows he can scroll } - getRenderItem = ({item}: {item: ServiceItemType}): React.Node => { + getRenderItem = ({item}: {item: ServiceItemType}) => { const {props} = this; - if (props.isHorizontal) + if (props.isHorizontal) { return ( { width={this.horizontalItemSize} /> ); + } return ; }; keyExtractor = (item: ServiceItemType): string => item.key; - render(): React.Node { + render() { const {props} = this; let containerStyle = {}; if (props.isHorizontal) { @@ -84,7 +82,7 @@ export default class CardList extends React.Component { } pagingEnabled={props.isHorizontal} snapToInterval={ - props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : null + props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : undefined } /> ); diff --git a/src/components/Lists/CardList/CardListItem.js b/src/components/Lists/CardList/CardListItem.tsx similarity index 53% rename from src/components/Lists/CardList/CardListItem.js rename to src/components/Lists/CardList/CardListItem.tsx index e3cae18..ce0e9f6 100644 --- a/src/components/Lists/CardList/CardListItem.js +++ b/src/components/Lists/CardList/CardListItem.tsx @@ -17,45 +17,38 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {Caption, Card, Paragraph, TouchableRipple} from 'react-native-paper'; import {View} from 'react-native'; import type {ServiceItemType} from '../../../managers/ServicesManager'; type PropsType = { - item: ServiceItemType, + item: ServiceItemType; }; -export default class CardListItem extends React.Component { - shouldComponentUpdate(): boolean { - return false; - } - - render(): React.Node { - const {props} = this; - const {item} = props; - const source = - typeof item.image === 'number' ? item.image : {uri: item.image}; - return ( - - - - - - {item.title} - {item.subtitle} - - - - - ); - } +function CardListItem(props: PropsType) { + const {item} = props; + const source = + typeof item.image === 'number' ? item.image : {uri: item.image}; + return ( + + + + + + {item.title} + {item.subtitle} + + + + + ); } + +export default React.memo(CardListItem, () => true); diff --git a/src/components/Lists/CardList/ImageListItem.js b/src/components/Lists/CardList/ImageListItem.js deleted file mode 100644 index bd855b7..0000000 --- a/src/components/Lists/CardList/ImageListItem.js +++ /dev/null @@ -1,73 +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 {Text, TouchableRipple} from 'react-native-paper'; -import {Image, View} from 'react-native'; -import type {ServiceItemType} from '../../../managers/ServicesManager'; - -type PropsType = { - item: ServiceItemType, - width: number, -}; - -export default class ImageListItem extends React.Component { - shouldComponentUpdate(): boolean { - return false; - } - - render(): React.Node { - const {props} = this; - const {item} = props; - const source = - typeof item.image === 'number' ? item.image : {uri: item.image}; - return ( - - - - - {item.title} - - - - ); - } -} diff --git a/src/components/Lists/CardList/ImageListItem.tsx b/src/components/Lists/CardList/ImageListItem.tsx new file mode 100644 index 0000000..73dd698 --- /dev/null +++ b/src/components/Lists/CardList/ImageListItem.tsx @@ -0,0 +1,66 @@ +/* + * 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, TouchableRipple} from 'react-native-paper'; +import {Image, View} from 'react-native'; +import type {ServiceItemType} from '../../../managers/ServicesManager'; + +type PropsType = { + item: ServiceItemType; + width: number; +}; + +function ImageListItem(props: PropsType) { + const {item} = props; + const source = + typeof item.image === 'number' ? item.image : {uri: item.image}; + return ( + + + + + {item.title} + + + + ); +} + +export default React.memo(ImageListItem, () => true); diff --git a/src/components/Lists/Clubs/ClubListHeader.js b/src/components/Lists/Clubs/ClubListHeader.tsx similarity index 60% rename from src/components/Lists/Clubs/ClubListHeader.js rename to src/components/Lists/Clubs/ClubListHeader.tsx index b1ebd86..d60030c 100644 --- a/src/components/Lists/Clubs/ClubListHeader.js +++ b/src/components/Lists/Clubs/ClubListHeader.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {Card, Chip, List, Text} from 'react-native-paper'; import {StyleSheet, View} from 'react-native'; @@ -26,12 +24,11 @@ import i18n from 'i18n-js'; import AnimatedAccordion from '../../Animations/AnimatedAccordion'; import {isItemInCategoryFilter} from '../../../utils/Search'; import type {ClubCategoryType} from '../../../screens/Amicale/Clubs/ClubListScreen'; -import type {ListIconPropsType} from '../../../constants/PaperStyles'; type PropsType = { - categories: Array, - onChipSelect: (id: number) => void, - selectedCategories: Array, + categories: Array; + onChipSelect: (id: number) => void; + selectedCategories: Array; }; const styles = StyleSheet.create({ @@ -54,16 +51,8 @@ const styles = StyleSheet.create({ }, }); -class ClubListHeader extends React.Component { - shouldComponentUpdate(nextProps: PropsType): boolean { - const {props} = this; - return ( - nextProps.selectedCategories.length !== props.selectedCategories.length - ); - } - - getChipRender = (category: ClubCategoryType, key: string): React.Node => { - const {props} = this; +function ClubListHeader(props: PropsType) { + const getChipRender = (category: ClubCategoryType, key: string) => { const onPress = (): void => props.onChipSelect(category.id); return ( { ); }; - getCategoriesRender(): React.Node { - const {props} = this; - const final = []; + const getCategoriesRender = () => { + const final: Array = []; props.categories.forEach((cat: ClubCategoryType) => { - final.push(this.getChipRender(cat, cat.id.toString())); + final.push(getChipRender(cat, cat.id.toString())); }); return final; - } + }; - render(): React.Node { - return ( - - ( - - )} - opened> - - {i18n.t('screens.clubs.categoriesFilterMessage')} - - {this.getCategoriesRender()} - - - ); - } + return ( + + ( + + )} + opened> + + {i18n.t('screens.clubs.categoriesFilterMessage')} + + {getCategoriesRender()} + + + ); } -export default ClubListHeader; +const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => { + return ( + prevProps.selectedCategories.length === nextProps.selectedCategories.length + ); +}; + +export default React.memo(ClubListHeader, areEqual); diff --git a/src/components/Lists/Clubs/ClubListItem.js b/src/components/Lists/Clubs/ClubListItem.tsx similarity index 86% rename from src/components/Lists/Clubs/ClubListItem.js rename to src/components/Lists/Clubs/ClubListItem.tsx index 76c592a..ad60cdf 100644 --- a/src/components/Lists/Clubs/ClubListItem.js +++ b/src/components/Lists/Clubs/ClubListItem.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {Avatar, Chip, List, withTheme} from 'react-native-paper'; import {View} from 'react-native'; @@ -26,14 +24,13 @@ import type { ClubCategoryType, ClubType, } from '../../../screens/Amicale/Clubs/ClubListScreen'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; type PropsType = { - onPress: () => void, - categoryTranslator: (id: number) => ClubCategoryType, - item: ClubType, - height: number, - theme: CustomThemeType, + onPress: () => void; + categoryTranslator: (id: number) => ClubCategoryType; + item: ClubType; + height: number; + theme: ReactNativePaper.Theme; }; class ClubListItem extends React.Component { @@ -48,9 +45,9 @@ class ClubListItem extends React.Component { return false; } - getCategoriesRender(categories: Array): React.Node { + getCategoriesRender(categories: Array) { const {props} = this; - const final = []; + const final: Array = []; categories.forEach((cat: number | null) => { if (cat != null) { const category: ClubCategoryType = props.categoryTranslator(cat); @@ -66,9 +63,9 @@ class ClubListItem extends React.Component { return {final}; } - render(): React.Node { + render() { const {props} = this; - const categoriesRender = (): React.Node => + const categoriesRender = () => this.getCategoriesRender(props.item.category); const {colors} = props.theme; return ( @@ -76,7 +73,7 @@ class ClubListItem extends React.Component { title={props.item.name} description={categoriesRender} onPress={props.onPress} - left={(): React.Node => ( + left={() => ( { source={{uri: props.item.logo}} /> )} - right={(): React.Node => ( + right={() => ( . - */ - -// @flow - -import * as React from 'react'; -import {withTheme} from 'react-native-paper'; -import {FlatList, Image, View} from 'react-native'; -import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; -import DashboardEditItem from './DashboardEditItem'; -import AnimatedAccordion from '../../Animations/AnimatedAccordion'; -import type { - ServiceCategoryType, - ServiceItemType, -} from '../../../managers/ServicesManager'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; - -type PropsType = { - item: ServiceCategoryType, - activeDashboard: Array, - onPress: (service: ServiceItemType) => void, - theme: CustomThemeType, -}; - -const LIST_ITEM_HEIGHT = 64; - -class DashboardEditAccordion extends React.Component { - getRenderItem = ({item}: {item: ServiceItemType}): React.Node => { - const {props} = this; - return ( - { - props.onPress(item); - }} - /> - ); - }; - - getItemLayout = ( - data: ?Array, - index: number, - ): {length: number, offset: number, index: number} => ({ - length: LIST_ITEM_HEIGHT, - offset: LIST_ITEM_HEIGHT * index, - index, - }); - - render(): React.Node { - const {props} = this; - const {item} = props; - return ( - - - typeof item.image === 'number' ? ( - - ) : ( - - ) - }> - {/* $FlowFixMe */} - - - - ); - } -} - -export default withTheme(DashboardEditAccordion); diff --git a/src/components/Lists/DashboardEdit/DashboardEditAccordion.tsx b/src/components/Lists/DashboardEdit/DashboardEditAccordion.tsx new file mode 100644 index 0000000..b273b9d --- /dev/null +++ b/src/components/Lists/DashboardEdit/DashboardEditAccordion.tsx @@ -0,0 +1,100 @@ +/* + * 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 {useTheme} from 'react-native-paper'; +import {FlatList, Image, View} from 'react-native'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import DashboardEditItem from './DashboardEditItem'; +import AnimatedAccordion from '../../Animations/AnimatedAccordion'; +import type { + ServiceCategoryType, + ServiceItemType, +} from '../../../managers/ServicesManager'; + +type PropsType = { + item: ServiceCategoryType; + activeDashboard: Array; + onPress: (service: ServiceItemType) => void; +}; + +const LIST_ITEM_HEIGHT = 64; + +function DashboardEditAccordion(props: PropsType) { + const theme = useTheme(); + + const getRenderItem = ({item}: {item: ServiceItemType}) => { + return ( + { + props.onPress(item); + }} + /> + ); + }; + + const getItemLayout = ( + data: Array | null | undefined, + index: number, + ): {length: number; offset: number; index: number} => ({ + length: LIST_ITEM_HEIGHT, + offset: LIST_ITEM_HEIGHT * index, + index, + }); + + const {item} = props; + return ( + + + typeof item.image === 'number' ? ( + + ) : ( + + ) + }> + + + + ); +} + +export default DashboardEditAccordion; diff --git a/src/components/Lists/DashboardEdit/DashboardEditItem.js b/src/components/Lists/DashboardEdit/DashboardEditItem.js deleted file mode 100644 index 0831fb6..0000000 --- a/src/components/Lists/DashboardEdit/DashboardEditItem.js +++ /dev/null @@ -1,81 +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 {Image} from 'react-native'; -import {List, withTheme} from 'react-native-paper'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; -import type {ServiceItemType} from '../../../managers/ServicesManager'; -import type {ListIconPropsType} from '../../../constants/PaperStyles'; - -type PropsType = { - item: ServiceItemType, - isActive: boolean, - height: number, - onPress: () => void, - theme: CustomThemeType, -}; - -class DashboardEditItem extends React.Component { - shouldComponentUpdate(nextProps: PropsType): boolean { - const {isActive} = this.props; - return nextProps.isActive !== isActive; - } - - render(): React.Node { - const {item, onPress, height, isActive, theme} = this.props; - return ( - ( - - )} - right={(props: ListIconPropsType): React.Node => - isActive ? ( - - ) : null - } - style={{ - height, - justifyContent: 'center', - paddingLeft: 30, - backgroundColor: isActive - ? theme.colors.proxiwashFinishedColor - : 'transparent', - }} - /> - ); - } -} - -export default withTheme(DashboardEditItem); diff --git a/src/components/Lists/DashboardEdit/DashboardEditItem.tsx b/src/components/Lists/DashboardEdit/DashboardEditItem.tsx new file mode 100644 index 0000000..78bade1 --- /dev/null +++ b/src/components/Lists/DashboardEdit/DashboardEditItem.tsx @@ -0,0 +1,76 @@ +/* + * 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 {Image} from 'react-native'; +import {List, useTheme} from 'react-native-paper'; +import type {ServiceItemType} from '../../../managers/ServicesManager'; + +type PropsType = { + item: ServiceItemType; + isActive: boolean; + height: number; + onPress: () => void; +}; + +function DashboardEditItem(props: PropsType) { + const theme = useTheme(); + const {item, onPress, height, isActive} = props; + return ( + ( + + )} + right={(iconProps) => + isActive ? ( + + ) : null + } + style={{ + height, + justifyContent: 'center', + paddingLeft: 30, + backgroundColor: isActive + ? theme.colors.proxiwashFinishedColor + : 'transparent', + }} + /> + ); +} + +const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => { + return nextProps.isActive !== prevProps.isActive; +}; + +export default React.memo(DashboardEditItem, areEqual); diff --git a/src/components/Lists/DashboardEdit/DashboardEditPreviewItem.js b/src/components/Lists/DashboardEdit/DashboardEditPreviewItem.js deleted file mode 100644 index 2d85a74..0000000 --- a/src/components/Lists/DashboardEdit/DashboardEditPreviewItem.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 . - */ - -// @flow - -import * as React from 'react'; -import {TouchableRipple, withTheme} from 'react-native-paper'; -import {Dimensions, Image, View} from 'react-native'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; - -type PropsType = { - image: string, - isActive: boolean, - onPress: () => void, - theme: CustomThemeType, -}; - -/** - * Component used to render a small dashboard item - */ -class DashboardEditPreviewItem extends React.Component { - itemSize: number; - - constructor(props: PropsType) { - super(props); - this.itemSize = Dimensions.get('window').width / 8; - } - - render(): React.Node { - const {props} = this; - return ( - - - - - - ); - } -} - -export default withTheme(DashboardEditPreviewItem); diff --git a/src/components/Lists/DashboardEdit/DashboardEditPreviewItem.tsx b/src/components/Lists/DashboardEdit/DashboardEditPreviewItem.tsx new file mode 100644 index 0000000..e787e76 --- /dev/null +++ b/src/components/Lists/DashboardEdit/DashboardEditPreviewItem.tsx @@ -0,0 +1,66 @@ +/* + * 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 {TouchableRipple, useTheme} from 'react-native-paper'; +import {Dimensions, Image, View} from 'react-native'; + +type PropsType = { + image: string; + isActive: boolean; + onPress: () => void; +}; + +/** + * Component used to render a small dashboard item + */ +function DashboardEditPreviewItem(props: PropsType) { + const theme = useTheme(); + const itemSize = Dimensions.get('window').width / 8; + + return ( + + + + + + ); +} + +export default DashboardEditPreviewItem; diff --git a/src/components/Lists/Equipment/EquipmentListItem.js b/src/components/Lists/Equipment/EquipmentListItem.js deleted file mode 100644 index c94dff4..0000000 --- a/src/components/Lists/Equipment/EquipmentListItem.js +++ /dev/null @@ -1,132 +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 {Avatar, List, withTheme} from 'react-native-paper'; -import i18n from 'i18n-js'; -import {StackNavigationProp} from '@react-navigation/stack'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; -import type {DeviceType} from '../../../screens/Amicale/Equipment/EquipmentListScreen'; -import { - getFirstEquipmentAvailability, - getRelativeDateString, - isEquipmentAvailable, -} from '../../../utils/EquipmentBooking'; - -type PropsType = { - navigation: StackNavigationProp, - userDeviceRentDates: [string, string], - item: DeviceType, - height: number, - theme: CustomThemeType, -}; - -class EquipmentListItem extends React.Component { - shouldComponentUpdate(nextProps: PropsType): boolean { - const {userDeviceRentDates} = this.props; - return nextProps.userDeviceRentDates !== userDeviceRentDates; - } - - render(): React.Node { - const {item, userDeviceRentDates, navigation, height, theme} = this.props; - const isRented = userDeviceRentDates != null; - const isAvailable = isEquipmentAvailable(item); - const firstAvailability = getFirstEquipmentAvailability(item); - - let onPress; - if (isRented) - onPress = () => { - navigation.navigate('equipment-confirm', { - item, - dates: userDeviceRentDates, - }); - }; - else - onPress = () => { - navigation.navigate('equipment-rent', {item}); - }; - - let description; - if (isRented) { - const start = new Date(userDeviceRentDates[0]); - const end = new Date(userDeviceRentDates[1]); - if (start.getTime() !== end.getTime()) - description = i18n.t('screens.equipment.bookingPeriod', { - begin: getRelativeDateString(start), - end: getRelativeDateString(end), - }); - else - description = i18n.t('screens.equipment.bookingDay', { - date: getRelativeDateString(start), - }); - } else if (isAvailable) - description = i18n.t('screens.equipment.bail', {cost: item.caution}); - else - description = i18n.t('screens.equipment.available', { - date: getRelativeDateString(firstAvailability), - }); - - let icon; - if (isRented) icon = 'bookmark-check'; - else if (isAvailable) icon = 'check-circle-outline'; - else icon = 'update'; - - let color; - if (isRented) color = theme.colors.warning; - else if (isAvailable) color = theme.colors.success; - else color = theme.colors.primary; - - return ( - ( - - )} - right={(): React.Node => ( - - )} - style={{ - height, - justifyContent: 'center', - }} - /> - ); - } -} - -export default withTheme(EquipmentListItem); diff --git a/src/components/Lists/Equipment/EquipmentListItem.tsx b/src/components/Lists/Equipment/EquipmentListItem.tsx new file mode 100644 index 0000000..19af142 --- /dev/null +++ b/src/components/Lists/Equipment/EquipmentListItem.tsx @@ -0,0 +1,136 @@ +/* + * 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 {Avatar, List, useTheme} from 'react-native-paper'; +import i18n from 'i18n-js'; +import {StackNavigationProp} from '@react-navigation/stack'; +import type {DeviceType} from '../../../screens/Amicale/Equipment/EquipmentListScreen'; +import { + getFirstEquipmentAvailability, + getRelativeDateString, + isEquipmentAvailable, +} from '../../../utils/EquipmentBooking'; + +type PropsType = { + navigation: StackNavigationProp; + userDeviceRentDates: [string, string]; + item: DeviceType; + height: number; +}; + +function EquipmentListItem(props: PropsType) { + const theme = useTheme(); + const {item, userDeviceRentDates, navigation, height} = props; + const isRented = userDeviceRentDates != null; + const isAvailable = isEquipmentAvailable(item); + const firstAvailability = getFirstEquipmentAvailability(item); + + let onPress; + if (isRented) { + onPress = () => { + navigation.navigate('equipment-confirm', { + item, + dates: userDeviceRentDates, + }); + }; + } else { + onPress = () => { + navigation.navigate('equipment-rent', {item}); + }; + } + + let description; + if (isRented) { + const start = new Date(userDeviceRentDates[0]); + const end = new Date(userDeviceRentDates[1]); + if (start.getTime() !== end.getTime()) { + description = i18n.t('screens.equipment.bookingPeriod', { + begin: getRelativeDateString(start), + end: getRelativeDateString(end), + }); + } else { + description = i18n.t('screens.equipment.bookingDay', { + date: getRelativeDateString(start), + }); + } + } else if (isAvailable) { + description = i18n.t('screens.equipment.bail', {cost: item.caution}); + } else { + description = i18n.t('screens.equipment.available', { + date: getRelativeDateString(firstAvailability), + }); + } + + let icon: string; + if (isRented) { + icon = 'bookmark-check'; + } else if (isAvailable) { + icon = 'check-circle-outline'; + } else { + icon = 'update'; + } + + let color: string; + if (isRented) { + color = theme.colors.warning; + } else if (isAvailable) { + color = theme.colors.success; + } else { + color = theme.colors.primary; + } + + return ( + ( + + )} + right={() => ( + + )} + style={{ + height, + justifyContent: 'center', + }} + /> + ); +} + +const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => { + return nextProps.userDeviceRentDates !== prevProps.userDeviceRentDates; +}; + +export default React.memo(EquipmentListItem, areEqual); diff --git a/src/components/Lists/PlanexGroups/GroupListAccordion.js b/src/components/Lists/PlanexGroups/GroupListAccordion.tsx similarity index 84% rename from src/components/Lists/PlanexGroups/GroupListAccordion.js rename to src/components/Lists/PlanexGroups/GroupListAccordion.tsx index e450f21..e403ebf 100644 --- a/src/components/Lists/PlanexGroups/GroupListAccordion.js +++ b/src/components/Lists/PlanexGroups/GroupListAccordion.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {List, withTheme} from 'react-native-paper'; import {FlatList, View} from 'react-native'; @@ -29,17 +27,15 @@ import type { PlanexGroupType, PlanexGroupCategoryType, } from '../../../screens/Planex/GroupSelectionScreen'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; -import type {ListIconPropsType} from '../../../constants/PaperStyles'; type PropsType = { - item: PlanexGroupCategoryType, - favorites: Array, - onGroupPress: (PlanexGroupType) => void, - onFavoritePress: (PlanexGroupType) => void, - currentSearchString: string, - height: number, - theme: CustomThemeType, + item: PlanexGroupCategoryType; + favorites: Array; + onGroupPress: (data: PlanexGroupType) => void; + onFavoritePress: (data: PlanexGroupType) => void; + currentSearchString: string; + height: number; + theme: ReactNativePaper.Theme; }; const LIST_ITEM_HEIGHT = 64; @@ -55,7 +51,7 @@ class GroupListAccordion extends React.Component { ); } - getRenderItem = ({item}: {item: PlanexGroupType}): React.Node => { + getRenderItem = ({item}: {item: PlanexGroupType}) => { const {props} = this; const onPress = () => { props.onGroupPress(item); @@ -77,18 +73,19 @@ class GroupListAccordion extends React.Component { getData(): Array { const {props} = this; const originalData = props.item.content; - const displayData = []; + const displayData: Array = []; originalData.forEach((data: PlanexGroupType) => { - if (stringMatchQuery(data.name, props.currentSearchString)) + if (stringMatchQuery(data.name, props.currentSearchString)) { displayData.push(data); + } }); return displayData; } itemLayout = ( - data: ?Array, + data: Array | null | undefined, index: number, - ): {length: number, offset: number, index: number} => ({ + ): {length: number; offset: number; index: number} => ({ length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index, @@ -96,7 +93,7 @@ class GroupListAccordion extends React.Component { keyExtractor = (item: PlanexGroupType): string => item.id.toString(); - render(): React.Node { + render() { const {props} = this; const {item} = this.props; return ( @@ -107,7 +104,7 @@ class GroupListAccordion extends React.Component { height: props.height, justifyContent: 'center', }} - left={(iconProps: ListIconPropsType): React.Node => + left={(iconProps) => item.id === 0 ? ( . */ -// @flow - import * as React from 'react'; import {List, TouchableRipple, withTheme} from 'react-native-paper'; import * as Animatable from 'react-native-animatable'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {PlanexGroupType} from '../../../screens/Planex/GroupSelectionScreen'; -import type {ListIconPropsType} from '../../../constants/PaperStyles'; type PropsType = { - theme: CustomThemeType, - onPress: () => void, - onStarPress: () => void, - item: PlanexGroupType, - favorites: Array, - height: number, + theme: ReactNativePaper.Theme; + onPress: () => void; + onStarPress: () => void; + item: PlanexGroupType; + favorites: Array; + height: number; }; const REPLACE_REGEX = /_/g; @@ -41,10 +37,11 @@ const REPLACE_REGEX = /_/g; class GroupListItem extends React.Component { isFav: boolean; - starRef: null | Animatable.View; + starRef: {current: null | Animatable.View}; constructor(props: PropsType) { super(props); + this.starRef = React.createRef(); this.isFav = this.isGroupInFavorites(props.favorites); } @@ -52,7 +49,9 @@ class GroupListItem extends React.Component { const {favorites} = this.props; const favChanged = favorites.length !== nextProps.favorites.length; let newFavState = this.isFav; - if (favChanged) newFavState = this.isGroupInFavorites(nextProps.favorites); + if (favChanged) { + newFavState = this.isGroupInFavorites(nextProps.favorites); + } const shouldUpdate = this.isFav !== newFavState; this.isFav = newFavState; return shouldUpdate; @@ -61,9 +60,12 @@ class GroupListItem extends React.Component { onStarPress = () => { const {props} = this; const ref = this.starRef; - if (ref != null) { - if (this.isFav) ref.rubberBand(); - else ref.swing(); + if (ref.current) { + if (this.isFav) { + ref.current.rubberBand(); + } else { + ref.current.swing(); + } } props.onStarPress(); }; @@ -71,31 +73,29 @@ class GroupListItem extends React.Component { isGroupInFavorites(favorites: Array): boolean { const {item} = this.props; for (let i = 0; i < favorites.length; i += 1) { - if (favorites[i].id === item.id) return true; + if (favorites[i].id === item.id) { + return true; + } } return false; } - render(): React.Node { + render() { const {props} = this; const {colors} = props.theme; return ( ( + left={(iconProps) => ( )} - right={(iconProps: ListIconPropsType): React.Node => ( - { - this.starRef = ref; - }} - useNativeDriver> + right={(iconProps) => ( + . - */ - -// @flow - -import * as React from 'react'; -import {Avatar, List, Text, withTheme} from 'react-native-paper'; -import i18n from 'i18n-js'; -import type {ProximoArticleType} from '../../../screens/Services/Proximo/ProximoMainScreen'; - -type PropsType = { - onPress: () => void, - color: string, - item: ProximoArticleType, - height: number, -}; - -class ProximoListItem extends React.Component { - shouldComponentUpdate(): boolean { - return false; - } - - render(): React.Node { - const {props} = this; - return ( - ( - - )} - right={(): React.Node => ( - {props.item.price}€ - )} - style={{ - height: props.height, - justifyContent: 'center', - }} - /> - ); - } -} - -export default withTheme(ProximoListItem); diff --git a/src/components/Lists/Proximo/ProximoListItem.tsx b/src/components/Lists/Proximo/ProximoListItem.tsx new file mode 100644 index 0000000..9465922 --- /dev/null +++ b/src/components/Lists/Proximo/ProximoListItem.tsx @@ -0,0 +1,59 @@ +/* + * 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 {Avatar, List, Text} from 'react-native-paper'; +import i18n from 'i18n-js'; +import type {ProximoArticleType} from '../../../screens/Services/Proximo/ProximoMainScreen'; + +type PropsType = { + onPress: () => void; + color: string; + item: ProximoArticleType; + height: number; +}; + +function ProximoListItem(props: PropsType) { + return ( + ( + + )} + right={() => ( + {props.item.price}€ + )} + style={{ + height: props.height, + justifyContent: 'center', + }} + /> + ); +} + +export default React.memo(ProximoListItem, () => true); diff --git a/src/components/Lists/Proxiwash/ProxiwashListItem.js b/src/components/Lists/Proxiwash/ProxiwashListItem.tsx similarity index 93% rename from src/components/Lists/Proxiwash/ProxiwashListItem.js rename to src/components/Lists/Proxiwash/ProxiwashListItem.tsx index 69132ce..9b64a59 100644 --- a/src/components/Lists/Proxiwash/ProxiwashListItem.js +++ b/src/components/Lists/Proxiwash/ProxiwashListItem.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import { Avatar, @@ -34,20 +32,19 @@ import i18n from 'i18n-js'; import * as Animatable from 'react-native-animatable'; import ProxiwashConstants from '../../../constants/ProxiwashConstants'; import AprilFoolsManager from '../../../managers/AprilFoolsManager'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {ProxiwashMachineType} from '../../../screens/Proxiwash/ProxiwashScreen'; type PropsType = { - item: ProxiwashMachineType, - theme: CustomThemeType, + item: ProxiwashMachineType; + theme: ReactNativePaper.Theme; onPress: ( title: string, item: ProxiwashMachineType, isDryer: boolean, - ) => void, - isWatched: boolean, - isDryer: boolean, - height: number, + ) => void; + isWatched: boolean; + isDryer: boolean; + height: number; }; const AnimatedIcon = Animatable.createAnimatableComponent(Avatar.Icon); @@ -89,10 +86,11 @@ class ProxiwashListItem extends React.Component { let displayNumber = props.item.number; const displayMaxWeight = props.item.maxWeight; - if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) + if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) { displayNumber = AprilFoolsManager.getProxiwashMachineDisplayNumber( parseInt(props.item.number, 10), ); + } this.title = props.isDryer ? i18n.t('screens.proxiwash.dryer') @@ -159,10 +157,10 @@ class ProxiwashListItem extends React.Component { colors.proxiwashUnknownColor; } - render(): React.Node { + render() { const {props} = this; const {colors} = props.theme; - const machineState = props.item.state; + const machineState = parseInt(props.item.state, 10); const isRunning = machineState === ProxiwashConstants.machineStates.RUNNING; const isReady = machineState === ProxiwashConstants.machineStates.AVAILABLE; const description = isRunning @@ -171,10 +169,13 @@ class ProxiwashListItem extends React.Component { const stateIcon = ProxiwashConstants.stateIcons[machineState]; const stateString = this.stateStrings[machineState]; let progress; - if (isRunning && props.item.donePercent !== '') + if (isRunning && props.item.donePercent !== '') { progress = parseFloat(props.item.donePercent) / 100; - else if (isRunning) progress = 0; - else progress = 1; + } else if (isRunning) { + progress = 0; + } else { + progress = 1; + } const icon = props.isWatched ? ( { justifyContent: 'center', }} onPress={this.onListItemPress} - left={(): React.Node => icon} - right={(): React.Node => ( + left={() => icon} + right={() => ( . */ -// @flow - import * as React from 'react'; import {Avatar, Text, withTheme} from 'react-native-paper'; import {StyleSheet, View} from 'react-native'; import i18n from 'i18n-js'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; type PropsType = { - theme: CustomThemeType, - title: string, - isDryer: boolean, - nbAvailable: number, + theme: ReactNativePaper.Theme; + title: string; + isDryer: boolean; + nbAvailable: number; }; const styles = StyleSheet.create({ @@ -61,7 +58,7 @@ class ProxiwashListItem extends React.Component { ); } - render(): React.Node { + render() { const {props} = this; const subtitle = `${props.nbAvailable} ${ props.nbAvailable <= 1 diff --git a/src/managers/ServicesManager.ts b/src/managers/ServicesManager.ts index b33237d..c1c6eb8 100644 --- a/src/managers/ServicesManager.ts +++ b/src/managers/ServicesManager.ts @@ -94,7 +94,7 @@ export type ServiceItemType = { key: string; title: string; subtitle: string; - image: string; + image: string | number; onPress: () => void; badgeFunction?: (dashboard: FullDashboardType) => number; };