From 1cc0802c12a7aae09cc3fd23f5bbbda672935145 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Tue, 4 Aug 2020 18:53:10 +0200 Subject: [PATCH] Improve Proxiwash components to match linter --- .../Lists/Proxiwash/ProxiwashListItem.js | 380 ++++---- .../Lists/Proxiwash/ProxiwashSectionHeader.js | 122 +-- src/screens/Proxiwash/ProxiwashAboutScreen.js | 178 ++-- src/screens/Proxiwash/ProxiwashScreen.js | 841 ++++++++++-------- src/utils/Proxiwash.js | 101 ++- 5 files changed, 882 insertions(+), 740 deletions(-) diff --git a/src/components/Lists/Proxiwash/ProxiwashListItem.js b/src/components/Lists/Proxiwash/ProxiwashListItem.js index a056fcd..bc22bdf 100644 --- a/src/components/Lists/Proxiwash/ProxiwashListItem.js +++ b/src/components/Lists/Proxiwash/ProxiwashListItem.js @@ -1,194 +1,236 @@ -import * as React from 'react'; -import {Avatar, Caption, List, ProgressBar, Surface, Text, withTheme} from 'react-native-paper'; -import {StyleSheet, View} from "react-native"; -import ProxiwashConstants from "../../../constants/ProxiwashConstants"; -import i18n from "i18n-js"; -import AprilFoolsManager from "../../../managers/AprilFoolsManager"; -import * as Animatable from "react-native-animatable"; -import type {CustomTheme} from "../../../managers/ThemeManager"; -import type {Machine} from "../../../screens/Proxiwash/ProxiwashScreen"; +// @flow -type Props = { - item: Machine, - theme: CustomTheme, - onPress: Function, - isWatched: boolean, +import * as React from 'react'; +import { + Avatar, + Caption, + List, + ProgressBar, + Surface, + Text, + withTheme, +} from 'react-native-paper'; +import {StyleSheet, View} from 'react-native'; +import i18n from 'i18n-js'; +import * as Animatable from 'react-native-animatable'; +import ProxiwashConstants from '../../../constants/ProxiwashConstants'; +import AprilFoolsManager from '../../../managers/AprilFoolsManager'; +import type {CustomTheme} from '../../../managers/ThemeManager'; +import type {ProxiwashMachineType} from '../../../screens/Proxiwash/ProxiwashScreen'; + +type PropsType = { + item: ProxiwashMachineType, + theme: CustomTheme, + onPress: ( + title: string, + item: ProxiwashMachineType, isDryer: boolean, - height: number, -} + ) => void, + isWatched: boolean, + isDryer: boolean, + height: number, +}; const AnimatedIcon = Animatable.createAnimatableComponent(Avatar.Icon); +const styles = StyleSheet.create({ + container: { + margin: 5, + justifyContent: 'center', + elevation: 1, + }, + icon: { + backgroundColor: 'transparent', + }, + progressBar: { + position: 'absolute', + left: 0, + borderRadius: 4, + }, +}); /** * Component used to display a proxiwash item, showing machine progression and state */ -class ProxiwashListItem extends React.Component { +class ProxiwashListItem extends React.Component { + stateColors: {[key: string]: string}; - stateColors: Object; - stateStrings: Object; + stateStrings: {[key: string]: string}; - title: string; + title: string; - constructor(props) { - super(props); - this.stateColors = {}; - this.stateStrings = {}; + constructor(props: PropsType) { + super(props); + this.stateColors = {}; + this.stateStrings = {}; - this.updateStateStrings(); + this.updateStateStrings(); - let displayNumber = props.item.number; - if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) - displayNumber = AprilFoolsManager.getProxiwashMachineDisplayNumber(parseInt(props.item.number)); + let displayNumber = props.item.number; + if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) + displayNumber = AprilFoolsManager.getProxiwashMachineDisplayNumber( + parseInt(props.item.number, 10), + ); - this.title = props.isDryer - ? i18n.t('screens.proxiwash.dryer') - : i18n.t('screens.proxiwash.washer'); - this.title += ' n°' + displayNumber; - } + this.title = props.isDryer + ? i18n.t('screens.proxiwash.dryer') + : i18n.t('screens.proxiwash.washer'); + this.title += ` n°${displayNumber}`; + } - shouldComponentUpdate(nextProps: Props): boolean { - const props = this.props; - return (nextProps.theme.dark !== props.theme.dark) - || (nextProps.item.state !== props.item.state) - || (nextProps.item.donePercent !== props.item.donePercent) - || (nextProps.isWatched !== props.isWatched); - } + shouldComponentUpdate(nextProps: PropsType): boolean { + const {props} = this; + return ( + nextProps.theme.dark !== props.theme.dark || + nextProps.item.state !== props.item.state || + nextProps.item.donePercent !== props.item.donePercent || + nextProps.isWatched !== props.isWatched + ); + } - updateStateStrings() { - this.stateStrings[ProxiwashConstants.machineStates.AVAILABLE] = i18n.t('screens.proxiwash.states.ready'); - this.stateStrings[ProxiwashConstants.machineStates.RUNNING] = i18n.t('screens.proxiwash.states.running'); - this.stateStrings[ProxiwashConstants.machineStates.RUNNING_NOT_STARTED] = i18n.t('screens.proxiwash.states.runningNotStarted'); - this.stateStrings[ProxiwashConstants.machineStates.FINISHED] = i18n.t('screens.proxiwash.states.finished'); - this.stateStrings[ProxiwashConstants.machineStates.UNAVAILABLE] = i18n.t('screens.proxiwash.states.broken'); - this.stateStrings[ProxiwashConstants.machineStates.ERROR] = i18n.t('screens.proxiwash.states.error'); - this.stateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t('screens.proxiwash.states.unknown'); - } + onListItemPress = () => { + const {props} = this; + props.onPress(this.title, props.item, props.isDryer); + }; - updateStateColors() { - const colors = this.props.theme.colors; - this.stateColors[ProxiwashConstants.machineStates.AVAILABLE] = colors.proxiwashReadyColor; - this.stateColors[ProxiwashConstants.machineStates.RUNNING] = colors.proxiwashRunningColor; - this.stateColors[ProxiwashConstants.machineStates.RUNNING_NOT_STARTED] = colors.proxiwashRunningNotStartedColor; - this.stateColors[ProxiwashConstants.machineStates.FINISHED] = colors.proxiwashFinishedColor; - this.stateColors[ProxiwashConstants.machineStates.UNAVAILABLE] = colors.proxiwashBrokenColor; - this.stateColors[ProxiwashConstants.machineStates.ERROR] = colors.proxiwashErrorColor; - this.stateColors[ProxiwashConstants.machineStates.UNKNOWN] = colors.proxiwashUnknownColor; - } + updateStateStrings() { + this.stateStrings[ProxiwashConstants.machineStates.AVAILABLE] = i18n.t( + 'screens.proxiwash.states.ready', + ); + this.stateStrings[ProxiwashConstants.machineStates.RUNNING] = i18n.t( + 'screens.proxiwash.states.running', + ); + this.stateStrings[ + ProxiwashConstants.machineStates.RUNNING_NOT_STARTED + ] = i18n.t('screens.proxiwash.states.runningNotStarted'); + this.stateStrings[ProxiwashConstants.machineStates.FINISHED] = i18n.t( + 'screens.proxiwash.states.finished', + ); + this.stateStrings[ProxiwashConstants.machineStates.UNAVAILABLE] = i18n.t( + 'screens.proxiwash.states.broken', + ); + this.stateStrings[ProxiwashConstants.machineStates.ERROR] = i18n.t( + 'screens.proxiwash.states.error', + ); + this.stateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t( + 'screens.proxiwash.states.unknown', + ); + } - onListItemPress = () => this.props.onPress(this.title, this.props.item, this.props.isDryer); + updateStateColors() { + const {props} = this; + const {colors} = props.theme; + this.stateColors[ProxiwashConstants.machineStates.AVAILABLE] = + colors.proxiwashReadyColor; + this.stateColors[ProxiwashConstants.machineStates.RUNNING] = + colors.proxiwashRunningColor; + this.stateColors[ProxiwashConstants.machineStates.RUNNING_NOT_STARTED] = + colors.proxiwashRunningNotStartedColor; + this.stateColors[ProxiwashConstants.machineStates.FINISHED] = + colors.proxiwashFinishedColor; + this.stateColors[ProxiwashConstants.machineStates.UNAVAILABLE] = + colors.proxiwashBrokenColor; + this.stateColors[ProxiwashConstants.machineStates.ERROR] = + colors.proxiwashErrorColor; + this.stateColors[ProxiwashConstants.machineStates.UNKNOWN] = + colors.proxiwashUnknownColor; + } - render() { - const props = this.props; - const colors = props.theme.colors; - const machineState = props.item.state; - const isRunning = machineState === ProxiwashConstants.machineStates.RUNNING; - const isReady = machineState === ProxiwashConstants.machineStates.AVAILABLE; - const description = isRunning ? props.item.startTime + '/' + props.item.endTime : ''; - const stateIcon = ProxiwashConstants.stateIcons[machineState]; - const stateString = this.stateStrings[machineState]; - const progress = isRunning - ? props.item.donePercent !== '' - ? parseFloat(props.item.donePercent) / 100 - : 0 - : 1; + render(): React.Node { + const {props} = this; + const {colors} = props.theme; + const machineState = props.item.state; + const isRunning = machineState === ProxiwashConstants.machineStates.RUNNING; + const isReady = machineState === ProxiwashConstants.machineStates.AVAILABLE; + const description = isRunning + ? `${props.item.startTime}/${props.item.endTime}` + : ''; + const stateIcon = ProxiwashConstants.stateIcons[machineState]; + const stateString = this.stateStrings[machineState]; + let progress; + if (isRunning && props.item.donePercent !== '') + progress = parseFloat(props.item.donePercent) / 100; + else if (isRunning) progress = 0; + else progress = 1; - const icon = props.isWatched - ? - : ; - this.updateStateColors(); - return ( - - { - !isReady - ? - : null - } - icon} - right={() => ( - - - - {stateString} - - { - machineState === ProxiwashConstants.machineStates.RUNNING - ? {props.item.remainingTime} min - : null - } - - - - - - )} + const icon = props.isWatched ? ( + + ) : ( + + ); + this.updateStateColors(); + return ( + + {!isReady ? ( + + ) : null} + icon} + right={(): React.Node => ( + + + + {stateString} + + {machineState === ProxiwashConstants.machineStates.RUNNING ? ( + {props.item.remainingTime} min + ) : null} + + + - - ); - } + + + )} + /> + + ); + } } -const styles = StyleSheet.create({ - container: { - margin: 5, - justifyContent: 'center', - elevation: 1 - }, - icon: { - backgroundColor: 'transparent' - }, - progressBar: { - position: 'absolute', - left: 0, - borderRadius: 4, - }, -}); - export default withTheme(ProxiwashListItem); diff --git a/src/components/Lists/Proxiwash/ProxiwashSectionHeader.js b/src/components/Lists/Proxiwash/ProxiwashSectionHeader.js index 68c70f4..3c7d2ef 100644 --- a/src/components/Lists/Proxiwash/ProxiwashSectionHeader.js +++ b/src/components/Lists/Proxiwash/ProxiwashSectionHeader.js @@ -1,72 +1,72 @@ +// @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 {StyleSheet, View} from 'react-native'; +import i18n from 'i18n-js'; +import type {CustomTheme} from '../../../managers/ThemeManager'; -type Props = { - title: string, - isDryer: boolean, - nbAvailable: number, -} +type PropsType = { + theme: CustomTheme, + title: string, + isDryer: boolean, + nbAvailable: number, +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + marginLeft: 5, + marginRight: 5, + marginBottom: 10, + marginTop: 20, + }, + icon: { + backgroundColor: 'transparent', + }, + text: { + fontSize: 20, + fontWeight: 'bold', + }, +}); /** * Component used to display a proxiwash item, showing machine progression and state */ -class ProxiwashListItem extends React.Component { +class ProxiwashListItem extends React.Component { + shouldComponentUpdate(nextProps: PropsType): boolean { + const {props} = this; + return ( + nextProps.theme.dark !== props.theme.dark || + nextProps.nbAvailable !== props.nbAvailable + ); + } - constructor(props) { - super(props); - } - - shouldComponentUpdate(nextProps: Props) { - return (nextProps.theme.dark !== this.props.theme.dark) - || (nextProps.nbAvailable !== this.props.nbAvailable) - } - - render() { - const props = this.props; - const subtitle = props.nbAvailable + ' ' + ( - (props.nbAvailable <= 1) - ? i18n.t('screens.proxiwash.numAvailable') - : i18n.t('screens.proxiwash.numAvailablePlural')); - const iconColor = props.nbAvailable > 0 - ? this.props.theme.colors.success - : this.props.theme.colors.primary; - return ( - - - - - {props.title} - - - {subtitle} - - - - ); - } + render(): React.Node { + const {props} = this; + const subtitle = `${props.nbAvailable} ${ + props.nbAvailable <= 1 + ? i18n.t('screens.proxiwash.numAvailable') + : i18n.t('screens.proxiwash.numAvailablePlural') + }`; + const iconColor = + props.nbAvailable > 0 + ? props.theme.colors.success + : props.theme.colors.primary; + return ( + + + + {props.title} + {subtitle} + + + ); + } } -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - marginLeft: 5, - marginRight: 5, - marginBottom: 10, - marginTop: 20, - }, - icon: { - backgroundColor: 'transparent' - }, - text: { - fontSize: 20, - fontWeight: 'bold', - } -}); - export default withTheme(ProxiwashListItem); diff --git a/src/screens/Proxiwash/ProxiwashAboutScreen.js b/src/screens/Proxiwash/ProxiwashAboutScreen.js index 59f45ac..2e50765 100644 --- a/src/screens/Proxiwash/ProxiwashAboutScreen.js +++ b/src/screens/Proxiwash/ProxiwashAboutScreen.js @@ -2,85 +2,117 @@ import * as React from 'react'; import {Image, View} from 'react-native'; -import i18n from "i18n-js"; +import i18n from 'i18n-js'; import {Card, List, Paragraph, Text, Title} from 'react-native-paper'; -import CustomTabBar from "../../components/Tabbar/CustomTabBar"; -import CollapsibleScrollView from "../../components/Collapsible/CollapsibleScrollView"; +import CustomTabBar from '../../components/Tabbar/CustomTabBar'; +import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView'; -type Props = {}; - -const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/Proxiwash.png"; +const LOGO = 'https://etud.insa-toulouse.fr/~amicale_app/images/Proxiwash.png'; /** * Class defining the proxiwash about screen. */ -export default class ProxiwashAboutScreen extends React.Component { +// eslint-disable-next-line react/prefer-stateless-function +export default class ProxiwashAboutScreen extends React.Component { + render(): React.Node { + return ( + + + + + {i18n.t('screens.proxiwash.description')} + + ( + + )} + /> + + {i18n.t('screens.proxiwash.procedure')} + {i18n.t('screens.proxiwash.dryerProcedure')} + {i18n.t('screens.proxiwash.tips')} + {i18n.t('screens.proxiwash.dryerTips')} + + - render() { - return ( - - - - - {i18n.t('screens.proxiwash.description')} - - } - /> - - {i18n.t('screens.proxiwash.procedure')} - {i18n.t('screens.proxiwash.dryerProcedure')} - {i18n.t('screens.proxiwash.tips')} - {i18n.t('screens.proxiwash.dryerTips')} - - + + ( + + )} + /> + + {i18n.t('screens.proxiwash.procedure')} + {i18n.t('screens.proxiwash.washerProcedure')} + {i18n.t('screens.proxiwash.tips')} + {i18n.t('screens.proxiwash.washerTips')} + + - - } - /> - - {i18n.t('screens.proxiwash.procedure')} - {i18n.t('screens.proxiwash.washerProcedure')} - {i18n.t('screens.proxiwash.tips')} - {i18n.t('screens.proxiwash.washerTips')} - - - - - } - /> - - {i18n.t('screens.proxiwash.washersTariff')} - {i18n.t('screens.proxiwash.dryersTariff')} - - - - } - /> - - {i18n.t('screens.proxiwash.paymentMethodsDescription')} - - - - ); - } + + ( + + )} + /> + + {i18n.t('screens.proxiwash.washersTariff')} + {i18n.t('screens.proxiwash.dryersTariff')} + + + + ( + + )} + /> + + + {i18n.t('screens.proxiwash.paymentMethodsDescription')} + + + + + ); + } } diff --git a/src/screens/Proxiwash/ProxiwashScreen.js b/src/screens/Proxiwash/ProxiwashScreen.js index 59ec568..986cf78 100644 --- a/src/screens/Proxiwash/ProxiwashScreen.js +++ b/src/screens/Proxiwash/ProxiwashScreen.js @@ -2,421 +2,480 @@ import * as React from 'react'; import {Alert, View} from 'react-native'; -import i18n from "i18n-js"; -import WebSectionList from "../../components/Screens/WebSectionList"; -import * as Notifications from "../../utils/Notifications"; -import AsyncStorageManager from "../../managers/AsyncStorageManager"; +import i18n from 'i18n-js'; import {Avatar, Button, Card, Text, withTheme} from 'react-native-paper'; -import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem"; -import ProxiwashConstants from "../../constants/ProxiwashConstants"; -import CustomModal from "../../components/Overrides/CustomModal"; -import AprilFoolsManager from "../../managers/AprilFoolsManager"; -import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton"; -import ProxiwashSectionHeader from "../../components/Lists/Proxiwash/ProxiwashSectionHeader"; -import type {CustomTheme} from "../../managers/ThemeManager"; -import {StackNavigationProp} from "@react-navigation/stack"; -import {getCleanedMachineWatched, getMachineEndDate, isMachineWatched} from "../../utils/Proxiwash"; -import {Modalize} from "react-native-modalize"; -import {MASCOT_STYLE} from "../../components/Mascot/Mascot"; -import MascotPopup from "../../components/Mascot/MascotPopup"; +import {StackNavigationProp} from '@react-navigation/stack'; +import {Modalize} from 'react-native-modalize'; +import WebSectionList from '../../components/Screens/WebSectionList'; +import * as Notifications from '../../utils/Notifications'; +import AsyncStorageManager from '../../managers/AsyncStorageManager'; +import ProxiwashListItem from '../../components/Lists/Proxiwash/ProxiwashListItem'; +import ProxiwashConstants from '../../constants/ProxiwashConstants'; +import CustomModal from '../../components/Overrides/CustomModal'; +import AprilFoolsManager from '../../managers/AprilFoolsManager'; +import MaterialHeaderButtons, { + Item, +} from '../../components/Overrides/CustomHeaderButton'; +import ProxiwashSectionHeader from '../../components/Lists/Proxiwash/ProxiwashSectionHeader'; +import type {CustomTheme} from '../../managers/ThemeManager'; +import { + getCleanedMachineWatched, + getMachineEndDate, + isMachineWatched, +} from '../../utils/Proxiwash'; +import {MASCOT_STYLE} from '../../components/Mascot/Mascot'; +import MascotPopup from '../../components/Mascot/MascotPopup'; +import type {SectionListDataType} from '../../components/Screens/WebSectionList'; -const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/v2/washinsa/washinsa_data.json"; +const DATA_URL = + 'https://etud.insa-toulouse.fr/~amicale_app/v2/washinsa/washinsa_data.json'; -let modalStateStrings = {}; +const modalStateStrings = {}; const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds const LIST_ITEM_HEIGHT = 64; -export type Machine = { - number: string, - state: string, - startTime: string, - endTime: string, - donePercent: string, - remainingTime: string, - program: string, -} - -type Props = { - navigation: StackNavigationProp, - theme: CustomTheme, -} - -type State = { - refreshing: boolean, - modalCurrentDisplayItem: React.Node, - machinesWatched: Array, +export type ProxiwashMachineType = { + number: string, + state: string, + startTime: string, + endTime: string, + donePercent: string, + remainingTime: string, + program: string, }; +type PropsType = { + navigation: StackNavigationProp, + theme: CustomTheme, +}; + +type StateType = { + modalCurrentDisplayItem: React.Node, + machinesWatched: Array, +}; /** * Class defining the app's proxiwash screen. This screen shows information about washing machines and * dryers, taken from a scrapper reading proxiwash website */ -class ProxiwashScreen extends React.Component { +class ProxiwashScreen extends React.Component { + /** + * Shows a warning telling the user notifications are disabled for the app + */ + static showNotificationsDisabledWarning() { + Alert.alert( + i18n.t('screens.proxiwash.modal.notificationErrorTitle'), + i18n.t('screens.proxiwash.modal.notificationErrorDescription'), + ); + } - modalRef: null | Modalize; + modalRef: null | Modalize; - fetchedData: { - dryers: Array, - washers: Array, + fetchedData: { + dryers: Array, + washers: Array, + }; + + /** + * Creates machine state parameters using current theme and translations + */ + constructor() { + super(); + this.state = { + modalCurrentDisplayItem: null, + machinesWatched: AsyncStorageManager.getObject( + AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key, + ), }; + modalStateStrings[ProxiwashConstants.machineStates.AVAILABLE] = i18n.t( + 'screens.proxiwash.modal.ready', + ); + modalStateStrings[ProxiwashConstants.machineStates.RUNNING] = i18n.t( + 'screens.proxiwash.modal.running', + ); + modalStateStrings[ + ProxiwashConstants.machineStates.RUNNING_NOT_STARTED + ] = i18n.t('screens.proxiwash.modal.runningNotStarted'); + modalStateStrings[ProxiwashConstants.machineStates.FINISHED] = i18n.t( + 'screens.proxiwash.modal.finished', + ); + modalStateStrings[ProxiwashConstants.machineStates.UNAVAILABLE] = i18n.t( + 'screens.proxiwash.modal.broken', + ); + modalStateStrings[ProxiwashConstants.machineStates.ERROR] = i18n.t( + 'screens.proxiwash.modal.error', + ); + modalStateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t( + 'screens.proxiwash.modal.unknown', + ); + } - state = { - refreshing: false, - modalCurrentDisplayItem: null, - machinesWatched: AsyncStorageManager.getObject(AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key), + /** + * Setup notification channel for android and add listeners to detect notifications fired + */ + componentDidMount() { + const {navigation} = this.props; + navigation.setOptions({ + headerRight: (): React.Node => ( + + + + ), + }); + } + + /** + * Callback used when pressing the about button. + * This will open the ProxiwashAboutScreen. + */ + onAboutPress = () => { + const {navigation} = this.props; + navigation.navigate('proxiwash-about'); + }; + + /** + * Callback used when the user clicks on enable notifications for a machine + * + * @param machine The machine to set notifications for + */ + onSetupNotificationsPress(machine: ProxiwashMachineType) { + if (this.modalRef) { + this.modalRef.close(); + } + this.setupNotifications(machine); + } + + /** + * Callback used when receiving modal ref + * + * @param ref + */ + onModalRef = (ref: Modalize) => { + this.modalRef = ref; + }; + + /** + * Generates the modal content. + * This shows information for the given machine. + * + * @param title The title to use + * @param item The item to display information for in the modal + * @param isDryer True if the given item is a dryer + * @return {*} + */ + getModalContent( + title: string, + item: ProxiwashMachineType, + isDryer: boolean, + ): React.Node { + const {props, state} = this; + let button = { + text: i18n.t('screens.proxiwash.modal.ok'), + icon: '', + onPress: undefined, }; + let message = modalStateStrings[item.state]; + const onPress = this.onSetupNotificationsPress.bind(this, item); + if (item.state === ProxiwashConstants.machineStates.RUNNING) { + let remainingTime = parseInt(item.remainingTime, 10); + if (remainingTime < 0) remainingTime = 0; - /** - * Creates machine state parameters using current theme and translations - */ - constructor(props) { - super(props); - modalStateStrings[ProxiwashConstants.machineStates.AVAILABLE] = i18n.t('screens.proxiwash.modal.ready'); - modalStateStrings[ProxiwashConstants.machineStates.RUNNING] = i18n.t('screens.proxiwash.modal.running'); - modalStateStrings[ProxiwashConstants.machineStates.RUNNING_NOT_STARTED] = i18n.t('screens.proxiwash.modal.runningNotStarted'); - modalStateStrings[ProxiwashConstants.machineStates.FINISHED] = i18n.t('screens.proxiwash.modal.finished'); - modalStateStrings[ProxiwashConstants.machineStates.UNAVAILABLE] = i18n.t('screens.proxiwash.modal.broken'); - modalStateStrings[ProxiwashConstants.machineStates.ERROR] = i18n.t('screens.proxiwash.modal.error'); - modalStateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t('screens.proxiwash.modal.unknown'); + button = { + text: isMachineWatched(item, state.machinesWatched) + ? i18n.t('screens.proxiwash.modal.disableNotifications') + : i18n.t('screens.proxiwash.modal.enableNotifications'), + icon: '', + onPress, + }; + message = i18n.t('screens.proxiwash.modal.running', { + start: item.startTime, + end: item.endTime, + remaining: remainingTime, + program: item.program, + }); + } else if (item.state === ProxiwashConstants.machineStates.AVAILABLE) { + if (isDryer) message += `\n${i18n.t('screens.proxiwash.dryersTariff')}`; + else message += `\n${i18n.t('screens.proxiwash.washersTariff')}`; } - - /** - * Setup notification channel for android and add listeners to detect notifications fired - */ - componentDidMount() { - this.props.navigation.setOptions({ - headerRight: () => - - - , - }); - } - - /** - * Callback used when pressing the about button. - * This will open the ProxiwashAboutScreen. - */ - onAboutPress = () => this.props.navigation.navigate('proxiwash-about'); - - /** - * Extracts the key for the given item - * - * @param item The item to extract the key from - * @return {*} The extracted key - */ - getKeyExtractor = (item: Machine) => item.number; - - /** - * Setups notifications for the machine with the given ID. - * One notification will be sent at the end of the program. - * Another will be send a few minutes before the end, based on the value of reminderNotifTime - * - * @param machine The machine to watch - */ - setupNotifications(machine: Machine) { - if (!isMachineWatched(machine, this.state.machinesWatched)) { - Notifications.setupMachineNotification(machine.number, true, getMachineEndDate(machine)) - .then(() => { - this.saveNotificationToState(machine); - }) - .catch(() => { - this.showNotificationsDisabledWarning(); - }); - } else { - Notifications.setupMachineNotification(machine.number, false, null) - .then(() => { - this.removeNotificationFromState(machine); - }); - } - } - - /** - * Shows a warning telling the user notifications are disabled for the app - */ - showNotificationsDisabledWarning() { - Alert.alert( - i18n.t("screens.proxiwash.modal.notificationErrorTitle"), - i18n.t("screens.proxiwash.modal.notificationErrorDescription"), - ); - } - - /** - * Adds the given notifications associated to a machine ID to the watchlist, and saves the array to the preferences - * - * @param machine - */ - saveNotificationToState(machine: Machine) { - let data = this.state.machinesWatched; - data.push(machine); - this.saveNewWatchedList(data); - } - - /** - * Removes the given index from the watchlist array and saves it to preferences - * - * @param machine - */ - removeNotificationFromState(machine: Machine) { - let data = this.state.machinesWatched; - for (let i = 0; i < data.length; i++) { - if (data[i].number === machine.number && data[i].endTime === machine.endTime) { - data.splice(i, 1); - break; - } - } - this.saveNewWatchedList(data); - } - - saveNewWatchedList(list: Array) { - this.setState({machinesWatched: list}); - AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key, list); - } - - /** - * Creates the dataset to be used by the flatlist - * - * @param fetchedData - * @return {*} - */ - createDataset = (fetchedData: Object) => { - let data = fetchedData; - if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) { - data = JSON.parse(JSON.stringify(fetchedData)); // Deep copy - AprilFoolsManager.getNewProxiwashDryerOrderedList(data.dryers); - AprilFoolsManager.getNewProxiwashWasherOrderedList(data.washers); - } - this.fetchedData = data; - this.state.machinesWatched = - getCleanedMachineWatched(this.state.machinesWatched, [...data.dryers, ...data.washers]); - return [ - { - title: i18n.t('screens.proxiwash.dryers'), - icon: 'tumble-dryer', - data: data.dryers === undefined ? [] : data.dryers, - keyExtractor: this.getKeyExtractor - }, - { - title: i18n.t('screens.proxiwash.washers'), - icon: 'washing-machine', - data: data.washers === undefined ? [] : data.washers, - keyExtractor: this.getKeyExtractor - }, - ]; - }; - - /** - * Shows a modal for the given item - * - * @param title The title to use - * @param item The item to display information for in the modal - * @param isDryer True if the given item is a dryer - */ - showModal = (title: string, item: Object, isDryer: boolean) => { - this.setState({ - modalCurrentDisplayItem: this.getModalContent(title, item, isDryer) - }); - if (this.modalRef) { - this.modalRef.open(); - } - }; - - /** - * Callback used when the user clicks on enable notifications for a machine - * - * @param machine The machine to set notifications for - */ - onSetupNotificationsPress(machine: Machine) { - if (this.modalRef) { - this.modalRef.close(); - } - this.setupNotifications(machine); - } - - /** - * Generates the modal content. - * This shows information for the given machine. - * - * @param title The title to use - * @param item The item to display information for in the modal - * @param isDryer True if the given item is a dryer - * @return {*} - */ - getModalContent(title: string, item: Machine, isDryer: boolean) { - let button = { - text: i18n.t("screens.proxiwash.modal.ok"), - icon: '', - onPress: undefined - }; - let message = modalStateStrings[item.state]; - const onPress = this.onSetupNotificationsPress.bind(this, item); - if (item.state === ProxiwashConstants.machineStates.RUNNING) { - let remainingTime = parseInt(item.remainingTime) - if (remainingTime < 0) - remainingTime = 0; - - button = - { - text: isMachineWatched(item, this.state.machinesWatched) ? - i18n.t("screens.proxiwash.modal.disableNotifications") : - i18n.t("screens.proxiwash.modal.enableNotifications"), - icon: '', - onPress: onPress - } - ; - message = i18n.t('screens.proxiwash.modal.running', - { - start: item.startTime, - end: item.endTime, - remaining: remainingTime, - program: item.program - }); - } else if (item.state === ProxiwashConstants.machineStates.AVAILABLE) { - if (isDryer) - message += '\n' + i18n.t('screens.proxiwash.dryersTariff'); - else - message += '\n' + i18n.t('screens.proxiwash.washersTariff'); - } - return ( - - } - - /> - - {message} - - - {button.onPress !== undefined ? - - - : null} - - ); - } - - /** - * Callback used when receiving modal ref - * - * @param ref - */ - onModalRef = (ref: Object) => { - this.modalRef = ref; - }; - - /** - * Gets the number of machines available - * - * @param isDryer True if we are only checking for dryer, false for washers - * @return {number} The number of machines available - */ - getMachineAvailableNumber(isDryer: boolean) { - let data; - if (isDryer) - data = this.fetchedData.dryers; - else - data = this.fetchedData.washers; - let count = 0; - for (let i = 0; i < data.length; i++) { - if (data[i].state === ProxiwashConstants.machineStates.AVAILABLE) - count += 1; - } - return count; - } - - /** - * Gets the section render item - * - * @param section The section to render - * @return {*} - */ - getRenderSectionHeader = ({section}: Object) => { - const isDryer = section.title === i18n.t('screens.proxiwash.dryers'); - const nbAvailable = this.getMachineAvailableNumber(isDryer); - return ( - - ); - }; - - /** - * Gets the list item to be rendered - * - * @param item The object containing the item's FetchedData - * @param section The object describing the current SectionList section - * @returns {React.Node} - */ - getRenderItem = ({item, section}: Object) => { - const isDryer = section.title === i18n.t('screens.proxiwash.dryers'); - return ( - + ( + - ); - }; + )} + /> + + {message} + - render() { - const nav = this.props.navigation; - return ( - - - - - - - {this.state.modalCurrentDisplayItem} - - - ); + {button.onPress !== undefined ? ( + + + + ) : null} + + ); + } + + /** + * Gets the section render item + * + * @param section The section to render + * @return {*} + */ + getRenderSectionHeader = ({ + section, + }: { + section: {title: string}, + }): React.Node => { + const isDryer = section.title === i18n.t('screens.proxiwash.dryers'); + const nbAvailable = this.getMachineAvailableNumber(isDryer); + return ( + + ); + }; + + /** + * Gets the list item to be rendered + * + * @param item The object containing the item's FetchedData + * @param section The object describing the current SectionList section + * @returns {React.Node} + */ + getRenderItem = ({ + item, + section, + }: { + item: ProxiwashMachineType, + section: {title: string}, + }): React.Node => { + const {machinesWatched} = this.state; + const isDryer = section.title === i18n.t('screens.proxiwash.dryers'); + return ( + + ); + }; + + /** + * Extracts the key for the given item + * + * @param item The item to extract the key from + * @return {*} The extracted key + */ + getKeyExtractor = (item: ProxiwashMachineType): string => item.number; + + /** + * Setups notifications for the machine with the given ID. + * One notification will be sent at the end of the program. + * Another will be send a few minutes before the end, based on the value of reminderNotifTime + * + * @param machine The machine to watch + */ + setupNotifications(machine: ProxiwashMachineType) { + const {machinesWatched} = this.state; + if (!isMachineWatched(machine, machinesWatched)) { + Notifications.setupMachineNotification( + machine.number, + true, + getMachineEndDate(machine), + ) + .then(() => { + this.saveNotificationToState(machine); + }) + .catch(() => { + ProxiwashScreen.showNotificationsDisabledWarning(); + }); + } else { + Notifications.setupMachineNotification(machine.number, false, null).then( + () => { + this.removeNotificationFromState(machine); + }, + ); } + } + + /** + * Gets the number of machines available + * + * @param isDryer True if we are only checking for dryer, false for washers + * @return {number} The number of machines available + */ + getMachineAvailableNumber(isDryer: boolean): number { + let data; + if (isDryer) data = this.fetchedData.dryers; + else data = this.fetchedData.washers; + let count = 0; + data.forEach((machine: ProxiwashMachineType) => { + if (machine.state === ProxiwashConstants.machineStates.AVAILABLE) + count += 1; + }); + return count; + } + + /** + * Creates the dataset to be used by the FlatList + * + * @param fetchedData + * @return {*} + */ + createDataset = (fetchedData: { + dryers: Array, + washers: Array, + }): SectionListDataType => { + const {state} = this; + let data = fetchedData; + if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) { + data = JSON.parse(JSON.stringify(fetchedData)); // Deep copy + AprilFoolsManager.getNewProxiwashDryerOrderedList(data.dryers); + AprilFoolsManager.getNewProxiwashWasherOrderedList(data.washers); + } + this.fetchedData = data; + this.state.machinesWatched = getCleanedMachineWatched( + state.machinesWatched, + [...data.dryers, ...data.washers], + ); + return [ + { + title: i18n.t('screens.proxiwash.dryers'), + icon: 'tumble-dryer', + data: data.dryers === undefined ? [] : data.dryers, + keyExtractor: this.getKeyExtractor, + }, + { + title: i18n.t('screens.proxiwash.washers'), + icon: 'washing-machine', + data: data.washers === undefined ? [] : data.washers, + keyExtractor: this.getKeyExtractor, + }, + ]; + }; + + /** + * Shows a modal for the given item + * + * @param title The title to use + * @param item The item to display information for in the modal + * @param isDryer True if the given item is a dryer + */ + showModal = (title: string, item: ProxiwashMachineType, isDryer: boolean) => { + this.setState({ + modalCurrentDisplayItem: this.getModalContent(title, item, isDryer), + }); + if (this.modalRef) { + this.modalRef.open(); + } + }; + + /** + * Adds the given notifications associated to a machine ID to the watchlist, and saves the array to the preferences + * + * @param machine + */ + saveNotificationToState(machine: ProxiwashMachineType) { + const {machinesWatched} = this.state; + const data = machinesWatched; + data.push(machine); + this.saveNewWatchedList(data); + } + + /** + * Removes the given index from the watchlist array and saves it to preferences + * + * @param selectedMachine + */ + removeNotificationFromState(selectedMachine: ProxiwashMachineType) { + const {machinesWatched} = this.state; + const newList = [...machinesWatched]; + machinesWatched.forEach((machine: ProxiwashMachineType, index: number) => { + if ( + machine.number === selectedMachine.number && + machine.endTime === selectedMachine.endTime + ) + newList.splice(index, 1); + }); + this.saveNewWatchedList(newList); + } + + saveNewWatchedList(list: Array) { + this.setState({machinesWatched: list}); + AsyncStorageManager.set( + AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key, + list, + ); + } + + render(): React.Node { + const {state} = this; + const {navigation} = this.props; + return ( + + + + + + + {state.modalCurrentDisplayItem} + + + ); + } } export default withTheme(ProxiwashScreen); diff --git a/src/utils/Proxiwash.js b/src/utils/Proxiwash.js index a891fd4..4b666a9 100644 --- a/src/utils/Proxiwash.js +++ b/src/utils/Proxiwash.js @@ -1,6 +1,6 @@ // @flow -import type {Machine} from "../screens/Proxiwash/ProxiwashScreen"; +import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen'; /** * Gets the machine end Date object. @@ -11,42 +11,43 @@ import type {Machine} from "../screens/Proxiwash/ProxiwashScreen"; * @param machine The machine to get the date from * @returns {Date} The date object representing the end time. */ -export function getMachineEndDate(machine: Machine): Date | null { - const array = machine.endTime.split(":"); - let endDate = new Date(Date.now()); - endDate.setHours(parseInt(array[0]), parseInt(array[1])); +export function getMachineEndDate(machine: ProxiwashMachineType): Date | null { + const array = machine.endTime.split(':'); + let endDate = new Date(Date.now()); + endDate.setHours(parseInt(array[0], 10), parseInt(array[1], 10)); - let limit = new Date(Date.now()); - if (endDate < limit) { - if (limit.getHours() > 12) { - limit.setHours(limit.getHours() - 12); - if (endDate < limit) - endDate.setDate(endDate.getDate() + 1); - else - endDate = null; - } else - endDate = null; - } + const limit = new Date(Date.now()); + if (endDate < limit) { + if (limit.getHours() > 12) { + limit.setHours(limit.getHours() - 12); + if (endDate < limit) endDate.setDate(endDate.getDate() + 1); + else endDate = null; + } else endDate = null; + } - return endDate; + return endDate; } /** * Checks whether the machine of the given ID has scheduled notifications * * @param machine The machine to check - * @param machineList The machine list + * @param machinesWatched The machine list * @returns {boolean} */ -export function isMachineWatched(machine: Machine, machineList: Array) { - let watched = false; - for (let i = 0; i < machineList.length; i++) { - if (machineList[i].number === machine.number && machineList[i].endTime === machine.endTime) { - watched = true; - break; - } - } - return watched; +export function isMachineWatched( + machine: ProxiwashMachineType, + machinesWatched: Array, +): boolean { + let watched = false; + machinesWatched.forEach((watchedMachine: ProxiwashMachineType) => { + if ( + watchedMachine.number === machine.number && + watchedMachine.endTime === machine.endTime + ) + watched = true; + }); + return watched; } /** @@ -54,14 +55,17 @@ export function isMachineWatched(machine: Machine, machineList: Array) * * @param id The machine's ID * @param allMachines The machine list - * @returns {null|Machine} The machine or null if not found + * @returns {null|ProxiwashMachineType} The machine or null if not found */ -export function getMachineOfId(id: string, allMachines: Array) { - for (let i = 0; i < allMachines.length; i++) { - if (allMachines[i].number === id) - return allMachines[i]; - } - return null; +export function getMachineOfId( + id: string, + allMachines: Array, +): ProxiwashMachineType | null { + let machineFound = null; + allMachines.forEach((machine: ProxiwashMachineType) => { + if (machine.number === id) machineFound = machine; + }); + return machineFound; } /** @@ -71,17 +75,22 @@ export function getMachineOfId(id: string, allMachines: Array) { * * @param machineWatchedList The current machine watch list * @param allMachines The current full machine list - * @returns {Array} + * @returns {Array} */ -export function getCleanedMachineWatched(machineWatchedList: Array, allMachines: Array) { - let newList = []; - for (let i = 0; i < machineWatchedList.length; i++) { - let machine = getMachineOfId(machineWatchedList[i].number, allMachines); - if (machine !== null - && machineWatchedList[i].number === machine.number - && machineWatchedList[i].endTime === machine.endTime) { - newList.push(machine); - } +export function getCleanedMachineWatched( + machineWatchedList: Array, + allMachines: Array, +): Array { + const newList = []; + machineWatchedList.forEach((watchedMachine: ProxiwashMachineType) => { + const machine = getMachineOfId(watchedMachine.number, allMachines); + if ( + machine != null && + watchedMachine.number === machine.number && + watchedMachine.endTime === machine.endTime + ) { + newList.push(machine); } - return newList; -} \ No newline at end of file + }); + return newList; +}