// @flow 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 {Avatar, Banner, 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 {withCollapsible} from "../../utils/withCollapsible"; import type {CustomTheme} from "../../managers/ThemeManager"; import {Collapsible} from "react-navigation-collapsible"; import {StackNavigationProp} from "@react-navigation/stack"; const PushNotification = require("react-native-push-notification"); const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json"; let modalStateStrings = {}; const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds const LIST_ITEM_HEIGHT = 64; type machine = { number: string, state: string, startTime: string, endTime: string, donePercent: string, remainingTime: string, } type Props = { navigation: StackNavigationProp, theme: CustomTheme, collapsibleStack: Collapsible, } type State = { refreshing: boolean, modalCurrentDisplayItem: React.Node, machinesWatched: Array, bannerVisible: boolean, }; /** * 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 { modalRef: Object; fetchedData: Object; allMachines: Array; state = { refreshing: false, modalCurrentDisplayItem: null, machinesWatched: JSON.parse(AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current), bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === '1', }; /** * Creates machine state parameters using current theme and translations */ constructor(props) { super(props); modalStateStrings[ProxiwashConstants.machineStates.TERMINE] = i18n.t('proxiwashScreen.modal.finished'); modalStateStrings[ProxiwashConstants.machineStates.DISPONIBLE] = i18n.t('proxiwashScreen.modal.ready'); modalStateStrings[ProxiwashConstants.machineStates["EN COURS"]] = i18n.t('proxiwashScreen.modal.running'); modalStateStrings[ProxiwashConstants.machineStates.HS] = i18n.t('proxiwashScreen.modal.broken'); modalStateStrings[ProxiwashConstants.machineStates.ERREUR] = i18n.t('proxiwashScreen.modal.error'); this.allMachines = []; } /** * Callback used when closing the banner. * This hides the banner and saves to preferences to prevent it from reopening */ onHideBanner = () => { this.setState({bannerVisible: false}); AsyncStorageManager.getInstance().savePref( AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.key, '0' ); }; /** * Setup notification channel for android and add listeners to detect notifications fired */ componentDidMount() { this.props.navigation.setOptions({ headerRight: this.getAboutButton, }); } /** * Callback used when pressing the about button. * This will open the ProxiwashAboutScreen. */ onAboutPress = () => this.props.navigation.navigate('proxiwash-about'); /** * Gets the about header button * * @return {*} */ getAboutButton = () => ; /** * 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; getMachineEndDate(machine: machine) { const array = machine.endTime.split(":"); let date = new Date(); date.setHours(parseInt(array[0]), parseInt(array[1])); if (date < new Date()) date.setDate(date.getDate() + 1); return date; } /** * 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 * @returns {Promise} */ setupNotifications(machine: machine) { if (!this.isMachineWatched(machine)) { Notifications.setupMachineNotification(machine.number, true, this.getMachineEndDate(machine)) .then(() => { this.saveNotificationToState(machine); }) .catch(() => { this.showNotificationsDisabledWarning(); }); } else { Notifications.setupMachineNotification(machine.number, false) .then(() => { this.removeNotificationFromState(machine); }); } } /** * Shows a warning telling the user notifications are disabled for the app */ showNotificationsDisabledWarning() { Alert.alert( i18n.t("proxiwashScreen.modal.notificationErrorTitle"), i18n.t("proxiwashScreen.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.getInstance().savePref( AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key, JSON.stringify(list), ); } /** * Checks whether the machine of the given ID has scheduled notifications * * @param machine * @returns {boolean} */ isMachineWatched(machine: machine) { let watched = false; const list = this.state.machinesWatched; for (let i = 0; i < list.length; i++) { if (list[i].number === machine.number && list[i].endTime === machine.endTime) { watched = true; break; } } return watched; } getMachineOfId(id: string) { for (let i = 0; i < this.allMachines.length; i++) { if (this.allMachines[i].number === id) return this.allMachines[i]; } return null; } getCleanedMachineWatched() { const list = this.state.machinesWatched; let newList = []; for (let i = 0; i < list.length; i++) { let machine = this.getMachineOfId(list[i].number); if (machine !== null && list[i].number === machine.number && list[i].endTime === machine.endTime && ProxiwashConstants.machineStates[list[i].state] === ProxiwashConstants.machineStates["EN COURS"]) { newList.push(machine); } } return newList; } /** * 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.allMachines = [...data.dryers, ...data.washers]; this.state.machinesWatched = this.getCleanedMachineWatched(); return [ { title: i18n.t('proxiwashScreen.dryers'), icon: 'tumble-dryer', data: data.dryers === undefined ? [] : data.dryers, keyExtractor: this.getKeyExtractor }, { title: i18n.t('proxiwashScreen.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: Object, isDryer: boolean) { let button = { text: i18n.t("proxiwashScreen.modal.ok"), icon: '', onPress: undefined }; let message = modalStateStrings[ProxiwashConstants.machineStates[item.state]]; const onPress = this.onSetupNotificationsPress.bind(this, item); if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates["EN COURS"]) { button = { text: this.isMachineWatched(item.number) ? i18n.t("proxiwashScreen.modal.disableNotifications") : i18n.t("proxiwashScreen.modal.enableNotifications"), icon: '', onPress: onPress } ; message = i18n.t('proxiwashScreen.modal.running', { start: item.startTime, end: item.endTime, remaining: item.remainingTime }); } else if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates.DISPONIBLE) { if (isDryer) message += '\n' + i18n.t('proxiwashScreen.dryersTariff'); else message += '\n' + i18n.t('proxiwashScreen.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 (ProxiwashConstants.machineStates[data[i].state] === ProxiwashConstants.machineStates["DISPONIBLE"]) 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('proxiwashScreen.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('proxiwashScreen.dryers'); return ( this.onSetupNotificationsPress(item)} isWatched={this.isMachineWatched(item)} isDryer={isDryer} height={LIST_ITEM_HEIGHT} /> ); }; render() { const nav = this.props.navigation; const {containerPaddingTop} = this.props.collapsibleStack; return ( } > {i18n.t('proxiwashScreen.enableNotificationsTip')} {this.state.modalCurrentDisplayItem} ); } } export default withCollapsible(withTheme(ProxiwashScreen));