/* * 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 React, { useLayoutEffect, useRef, useState } from 'react'; import { Linking, SectionListData, SectionListRenderItemInfo, StyleSheet, View, } from 'react-native'; import i18n from 'i18n-js'; import { Avatar, Button, Card, Text, useTheme } from 'react-native-paper'; import { Modalize } from 'react-native-modalize'; import WebSectionList from '../../components/Screens/WebSectionList'; import ProxiwashListItem from '../../components/Lists/Proxiwash/ProxiwashListItem'; import ProxiwashConstants, { MachineStates, } 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 { 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'; import type { LaundromatType } from './ProxiwashAboutScreen'; import GENERAL_STYLES from '../../constants/Styles'; import { readData } from '../../utils/WebData'; import { useNavigation } from '@react-navigation/core'; import { setupMachineNotification } from '../../utils/Notifications'; import ProxiwashListHeader from '../../components/Lists/Proxiwash/ProxiwashListHeader'; import { getPreferenceNumber, getPreferenceObject, getPreferenceString, ProxiwashPreferenceKeys, } from '../../utils/asyncStorage'; import { useProxiwashPreferences } from '../../context/preferencesContext'; import { useSubsequentEffect } from '../../utils/customHooks'; const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds const LIST_ITEM_HEIGHT = 64; export type ProxiwashMachineType = { number: string; state: MachineStates; maxWeight: number; startTime: string; endTime: string; donePercent: string; remainingTime: string; program: string; }; export type ProxiwashInfoType = { message: string; last_checked: number; }; type FetchedDataType = { info: ProxiwashInfoType; dryers: Array; washers: Array; }; const styles = StyleSheet.create({ modalContainer: { flex: 1, padding: 20, }, icon: { backgroundColor: 'transparent', }, container: { position: 'absolute', width: '100%', height: '100%', }, }); function ProxiwashScreen() { const navigation = useNavigation(); const theme = useTheme(); const { preferences, updatePreferences } = useProxiwashPreferences(); const [modalCurrentDisplayItem, setModalCurrentDisplayItem] = useState(null); const reminder = getPreferenceNumber( ProxiwashPreferenceKeys.proxiwashNotifications, preferences ); const [refresh, setRefresh] = useState(false); const getMachinesWatched = () => { const data = getPreferenceObject( ProxiwashPreferenceKeys.proxiwashWatchedMachines, preferences ) as Array; return data ? (data as Array) : []; }; const getSelectedWash = () => { const data = getPreferenceString( ProxiwashPreferenceKeys.selectedWash, preferences ); if (data !== 'washinsa' && data !== 'tripodeB') { return 'washinsa'; } else { return data; } }; const machinesWatched: Array = getMachinesWatched(); const selectedWash: 'washinsa' | 'tripodeB' = getSelectedWash(); useSubsequentEffect(() => { // Refresh the list when the selected wash changes setRefresh(true); }, [selectedWash]); const modalStateStrings: { [key in MachineStates]: string } = { [MachineStates.AVAILABLE]: i18n.t('screens.proxiwash.modal.ready'), [MachineStates.RUNNING]: i18n.t('screens.proxiwash.modal.running'), [MachineStates.RUNNING_NOT_STARTED]: i18n.t( 'screens.proxiwash.modal.runningNotStarted' ), [MachineStates.FINISHED]: i18n.t('screens.proxiwash.modal.finished'), [MachineStates.UNAVAILABLE]: i18n.t('screens.proxiwash.modal.broken'), [MachineStates.ERROR]: i18n.t('screens.proxiwash.modal.error'), [MachineStates.UNKNOWN]: i18n.t('screens.proxiwash.modal.unknown'), }; const modalRef = useRef(null); useLayoutEffect(() => { navigation.setOptions({ headerRight: () => ( Linking.openURL(ProxiwashConstants[selectedWash].webPageUrl) } /> navigation.navigate('proxiwash-about')} /> ), }); }, [navigation, selectedWash]); /** * Callback used when the user clicks on enable notifications for a machine * * @param machine The machine to set notifications for */ const onSetupNotificationsPress = (machine: ProxiwashMachineType) => { if (modalRef.current) { modalRef.current.close(); } 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 {*} */ const getModalContent = ( title: string, item: ProxiwashMachineType, isDryer: boolean ) => { let button: { text: string; icon: string; onPress: () => void } | undefined; let message = modalStateStrings[item.state]; const onPress = () => onSetupNotificationsPress(item); if (item.state === MachineStates.RUNNING) { let remainingTime = parseInt(item.remainingTime, 10); if (remainingTime < 0) { remainingTime = 0; } button = { text: isMachineWatched(item, 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, }); } return ( ( )} /> {message} {button ? ( ) : null} ); }; /** * Gets the section render item * * @param section The section to render * @return {*} */ const getRenderSectionHeader = ({ section, }: { section: SectionListData; }) => { const isDryer = section.title === i18n.t('screens.proxiwash.dryers'); const nbAvailable = getMachineAvailableNumber(section.data); 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} */ const getRenderItem = ( data: SectionListRenderItemInfo ) => { const isDryer = data.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 */ const 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 */ const setupNotifications = (machine: ProxiwashMachineType) => { if (!isMachineWatched(machine, machinesWatched)) { setupMachineNotification( machine.number, true, reminder, getMachineEndDate(machine) ); saveNotificationToState(machine); } else { setupMachineNotification(machine.number, false); 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 */ const getMachineAvailableNumber = ( data: ReadonlyArray ): number => { let count = 0; data.forEach((machine: ProxiwashMachineType) => { if (machine.state === MachineStates.AVAILABLE) { count += 1; } }); return count; }; /** * Creates the dataset to be used by the FlatList * * @param fetchedData * @return {*} */ const createDataset = ( fetchedData: FetchedDataType | undefined ): SectionListDataType => { if (fetchedData) { let data = fetchedData; if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) { data = JSON.parse(JSON.stringify(fetchedData)); // Deep copy AprilFoolsManager.getNewProxiwashDryerOrderedList(data.dryers); AprilFoolsManager.getNewProxiwashWasherOrderedList(data.washers); } fetchedData = data; const cleanedList = getCleanedMachineWatched(machinesWatched, [ ...data.dryers, ...data.washers, ]); if (cleanedList.length !== machinesWatched.length) { updatePreferences( ProxiwashPreferenceKeys.proxiwashWatchedMachines, cleanedList ); } return [ { title: i18n.t('screens.proxiwash.dryers'), icon: 'tumble-dryer', data: data.dryers === undefined ? [] : data.dryers, keyExtractor: getKeyExtractor, }, { title: i18n.t('screens.proxiwash.washers'), icon: 'washing-machine', data: data.washers === undefined ? [] : data.washers, keyExtractor: getKeyExtractor, }, ]; } else { return []; } }; /** * 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 */ const showModal = ( title: string, item: ProxiwashMachineType, isDryer: boolean ) => { setModalCurrentDisplayItem(getModalContent(title, item, isDryer)); if (modalRef.current) { modalRef.current.open(); } }; /** * Adds the given notifications associated to a machine ID to the watchlist, and saves the array to the preferences * * @param machine */ const saveNotificationToState = (machine: ProxiwashMachineType) => { let data = [...machinesWatched]; data.push(machine); saveNewWatchedList(data); }; /** * Removes the given index from the watchlist array and saves it to preferences * * @param selectedMachine */ const removeNotificationFromState = ( selectedMachine: ProxiwashMachineType ) => { const newList = machinesWatched.filter( (m) => m.number !== selectedMachine.number ); saveNewWatchedList(newList); }; const saveNewWatchedList = (list: Array) => { updatePreferences(ProxiwashPreferenceKeys.proxiwashWatchedMachines, list); }; const renderListHeaderComponent = ( data: FetchedDataType | undefined, _loading: boolean, lastRefreshDate: Date | undefined ) => { if (data) { return ( ); } else { return null; } }; let data: LaundromatType; switch (selectedWash) { case 'tripodeB': data = ProxiwashConstants.tripodeB; break; default: data = ProxiwashConstants.washinsa; } return ( readData(data.url)} createDataset={createDataset} renderItem={getRenderItem} renderSectionHeader={getRenderSectionHeader} autoRefreshTime={REFRESH_TIME} refreshOnFocus={true} extraData={machinesWatched.length} renderListHeaderComponent={renderListHeaderComponent} refresh={refresh} onFinish={() => setRefresh(false)} /> navigation.navigate('settings'), }, cancel: { message: i18n.t('screens.proxiwash.mascotDialog.cancel'), icon: 'close', }, }} emotion={MASCOT_STYLE.NORMAL} /> {modalCurrentDisplayItem} ); } export default ProxiwashScreen;