123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- // @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";
- import {getCleanedMachineWatched, getMachineEndDate, isMachineWatched} from "../../utils/Proxiwash";
- import {Modalize} from "react-native-modalize";
-
- const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/v2/washinsa/washinsa_data.json";
-
- let 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,
- collapsibleStack: Collapsible,
- }
-
- type State = {
- refreshing: boolean,
- modalCurrentDisplayItem: React.Node,
- machinesWatched: Array<Machine>,
- 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<Props, State> {
-
- modalRef: null | Modalize;
-
- fetchedData: {
- dryers: Array<Machine>,
- washers: Array<Machine>,
- };
-
- state = {
- refreshing: false,
- modalCurrentDisplayItem: null,
- machinesWatched: JSON.parse(AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current),
- bannerVisible: false,
- };
-
- /**
- * Creates machine state parameters using current theme and translations
- */
- constructor(props) {
- super(props);
- modalStateStrings[ProxiwashConstants.machineStates.AVAILABLE] = i18n.t('proxiwashScreen.modal.ready');
- modalStateStrings[ProxiwashConstants.machineStates.RUNNING] = i18n.t('proxiwashScreen.modal.running');
- modalStateStrings[ProxiwashConstants.machineStates.RUNNING_NOT_STARTED] = i18n.t('proxiwashScreen.modal.runningNotStarted');
- modalStateStrings[ProxiwashConstants.machineStates.FINISHED] = i18n.t('proxiwashScreen.modal.finished');
- modalStateStrings[ProxiwashConstants.machineStates.UNAVAILABLE] = i18n.t('proxiwashScreen.modal.broken');
- modalStateStrings[ProxiwashConstants.machineStates.ERROR] = i18n.t('proxiwashScreen.modal.error');
- modalStateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t('proxiwashScreen.modal.unknown');
- }
-
- /**
- * 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: () =>
- <MaterialHeaderButtons>
- <Item title="information" iconName="information" onPress={this.onAboutPress}/>
- </MaterialHeaderButtons>,
- });
- setTimeout(this.onBannerTimeout, 2000);
- }
-
- onBannerTimeout = () => {
- this.setState({bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === "1"})
- }
-
- /**
- * 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("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<Machine>) {
- this.setState({machinesWatched: list});
- AsyncStorageManager.getInstance().savePref(
- AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key,
- JSON.stringify(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('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: Machine, isDryer: boolean) {
- let button = {
- text: i18n.t("proxiwashScreen.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("proxiwashScreen.modal.disableNotifications") :
- i18n.t("proxiwashScreen.modal.enableNotifications"),
- icon: '',
- onPress: onPress
- }
- ;
- message = i18n.t('proxiwashScreen.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('proxiwashScreen.dryersTariff');
- else
- message += '\n' + i18n.t('proxiwashScreen.washersTariff');
- }
- return (
- <View style={{
- flex: 1,
- padding: 20
- }}>
- <Card.Title
- title={title}
- left={() => <Avatar.Icon
- icon={isDryer ? 'tumble-dryer' : 'washing-machine'}
- color={this.props.theme.colors.text}
- style={{backgroundColor: 'transparent'}}/>}
-
- />
- <Card.Content>
- <Text>{message}</Text>
- </Card.Content>
-
- {button.onPress !== undefined ?
- <Card.Actions>
- <Button
- icon={button.icon}
- mode="contained"
- onPress={button.onPress}
- style={{marginLeft: 'auto', marginRight: 'auto'}}
- >
- {button.text}
- </Button>
- </Card.Actions> : null}
- </View>
- );
- }
-
- /**
- * 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('proxiwashScreen.dryers');
- const nbAvailable = this.getMachineAvailableNumber(isDryer);
- return (
- <ProxiwashSectionHeader
- title={section.title}
- nbAvailable={nbAvailable}
- isDryer={isDryer}/>
- );
- };
-
- /**
- * 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 (
- <ProxiwashListItem
- item={item}
- onPress={this.showModal}
- isWatched={isMachineWatched(item, this.state.machinesWatched)}
- isDryer={isDryer}
- height={LIST_ITEM_HEIGHT}
- />
- );
- };
-
- render() {
- const nav = this.props.navigation;
- const {containerPaddingTop} = this.props.collapsibleStack;
- return (
- <View
- style={{flex: 1}}
- >
- <View style={{
- position: "absolute",
- width: "100%",
- height: "100%",
- }}>
- <WebSectionList
- createDataset={this.createDataset}
- navigation={nav}
- fetchUrl={DATA_URL}
- renderItem={this.getRenderItem}
- renderSectionHeader={this.getRenderSectionHeader}
- autoRefreshTime={REFRESH_TIME}
- refreshOnFocus={true}
- updateData={this.state.machinesWatched.length}/>
- </View>
- <Banner
- style={{
- marginTop: containerPaddingTop,
- backgroundColor: this.props.theme.colors.surface
- }}
- visible={this.state.bannerVisible}
- actions={[
- {
- label: i18n.t('proxiwashScreen.bannerButton'),
- onPress: this.onHideBanner,
- },
- ]}
- icon={() => <Avatar.Icon
- icon={'bell'}
- size={40}
- />}
- >
- {i18n.t('proxiwashScreen.enableNotificationsTip')}
- </Banner>
- <CustomModal onRef={this.onModalRef}>
- {this.state.modalCurrentDisplayItem}
- </CustomModal>
- </View>
- );
- }
- }
-
- export default withCollapsible(withTheme(ProxiwashScreen));
|