diff --git a/dataProxiwash.json b/dataProxiwash.json
new file mode 100644
index 0000000..e874008
--- /dev/null
+++ b/dataProxiwash.json
@@ -0,0 +1,90 @@
+{
+ "dryers": [
+ {
+ "number": "1",
+ "state": "TERMINE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "2",
+ "state": "DISPONIBLE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "3",
+ "state": "FONCTIONNE",
+ "startTime": "10:16",
+ "endTime": "10:50",
+ "donePercent": "36.5"
+ }
+ ],
+ "washers": [
+ {
+ "number": "4",
+ "state": "DISPONIBLE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "5",
+ "state": "DISPONIBLE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "6",
+ "state": "DISPONIBLE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "7",
+ "state": "DISPONIBLE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "8",
+ "state": "DISPONIBLE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "9",
+ "state": "FONCTIONNE",
+ "startTime": "09:46",
+ "endTime": "10:31",
+ "donePercent": "93.9"
+ },
+ {
+ "number": "10",
+ "state": "DISPONIBLE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "11",
+ "state": "HS",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ },
+ {
+ "number": "12",
+ "state": "DISPONIBLE",
+ "startTime": "",
+ "endTime": "",
+ "donePercent": ""
+ }
+ ]
+}
diff --git a/package.json b/package.json
index f64c7bd..ec49a77 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"expo": "^33.0.0",
"expo-font": "^5.0.1",
"expo-localization": "^5.0.1",
+ "expo-permissions": "^5.0.1",
"i18n-js": "^3.3.0",
"i18next": "latest",
"native-base": "latest",
@@ -20,13 +21,13 @@
"react-i18next": "latest",
"react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
"react-native-paper": "latest",
+ "react-native-platform-touchable": "latest",
"react-native-settings-page": "latest",
"react-native-status-bar-height": "latest",
"react-native-web": "^0.11.4",
"react-native-week-view": "latest",
"react-navigation": "latest",
- "react-navigation-material-bottom-tabs": "latest",
- "react-native-platform-touchable": "latest"
+ "react-navigation-material-bottom-tabs": "latest"
},
"devDependencies": {
"babel-preset-expo": "^5.1.1",
diff --git a/screens/HomeScreen.js b/screens/HomeScreen.js
index 222c821..b320465 100644
--- a/screens/HomeScreen.js
+++ b/screens/HomeScreen.js
@@ -2,7 +2,7 @@ import React from 'react';
import {Container, Content, Text, Button, Icon} from 'native-base';
import CustomHeader from '../components/CustomHeader';
import i18n from "i18n-js";
-
+import NotificationsManager from '../utils/NotificationsManager'
import { Notifications } from 'expo';
@@ -13,7 +13,7 @@ export default class HomeScreen extends React.Component {
-
);
}
diff --git a/screens/ProxiwashScreen.js b/screens/ProxiwashScreen.js
index f0b92a5..d6880cf 100644
--- a/screens/ProxiwashScreen.js
+++ b/screens/ProxiwashScreen.js
@@ -1,24 +1,240 @@
import React from 'react';
-import {StyleSheet, View} from 'react-native';
-import {Container, Text} from 'native-base';
+import {SectionList, RefreshControl, StyleSheet, View, ScrollView} from 'react-native';
+import {Badge, Body, Container, Content, Icon, Left, ListItem, Right, Text, Toast, H2, Button} from 'native-base';
import CustomHeader from "../components/CustomHeader";
+import NotificationsManager from '../utils/NotificationsManager';
+import i18n from "i18n-js";
+import {AsyncStorage} from 'react-native'
+
+const DATA_URL = "https://etud.insa-toulouse.fr/~vergnet/appli-amicale/dataProxiwash.json";
+const WATCHED_MACHINES_PREFKEY = "proxiwash.watchedMachines";
+
+const remainderNotifTime = 5;
+
+const MACHINE_STATES = {
+ TERMINE: "0",
+ DISPONIBLE: "1",
+ FONCTIONNE: "2",
+ HS: "3",
+ ERREUR: "4"
+};
+
+let stateStrings = {};
+
+let stateColors = {};
+
export default class ProxiwashScreen extends React.Component {
+
+ constructor(props) {
+ super(props);
+ stateColors[MACHINE_STATES.TERMINE] = 'rgba(54,165,22,0.4)';
+ stateColors[MACHINE_STATES.DISPONIBLE] = '#ffffff';
+ stateColors[MACHINE_STATES.FONCTIONNE] = 'rgba(241,237,41,0.4)';
+ stateColors[MACHINE_STATES.HS] = '#a2a2a2';
+ stateColors[MACHINE_STATES.ERREUR] = 'rgba(204,7,0,0.4)';
+
+ stateStrings[MACHINE_STATES.TERMINE] = i18n.t('proxiwashScreen.states.finished');
+ stateStrings[MACHINE_STATES.DISPONIBLE] = i18n.t('proxiwashScreen.states.ready');
+ stateStrings[MACHINE_STATES.FONCTIONNE] = i18n.t('proxiwashScreen.states.running');
+ stateStrings[MACHINE_STATES.HS] = i18n.t('proxiwashScreen.states.broken');
+ stateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.states.error');
+ this.state = {
+ refreshing: false,
+ data: {},
+ machinesWatched: [],
+ };
+ }
+
+ async readData() {
+ try {
+ let response = await fetch(DATA_URL);
+ let responseJson = await response.json();
+ // This prevents end notifications from showing
+ // let watchList = this.state.machinesWatched;
+ // for (let i = 0; i < watchList.length; i++) {
+ // if (responseJson[MACHINE_STATES[watchList[i].machineNumber.state]] !== MACHINE_STATES.FONCTIONNE)
+ // this.disableNotification(watchList[i].machineNumber);
+ // }
+ this.setState({
+ data: responseJson
+ });
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ async componentWillMount() {
+ let dataString = await AsyncStorage.getItem(WATCHED_MACHINES_PREFKEY);
+ if (dataString === null)
+ dataString = '[]';
+ this.setState({machinesWatched: JSON.parse(dataString)});
+ }
+
+ componentDidMount() {
+ this._onRefresh();
+ }
+
+ _onRefresh = () => {
+ this.setState({refreshing: true});
+ this.readData().then(() => {
+ this.setState({refreshing: false});
+ Toast.show({
+ text: i18n.t('proxiwashScreen.listUpdated'),
+ buttonText: 'OK',
+ type: "success",
+ duration: 2000
+ })
+ });
+ };
+
+ static getRemainingTime(startString, endString, percentDone) {
+ let startArray = startString.split(':');
+ let endArray = endString.split(':');
+ let startDate = new Date();
+ startDate.setHours(parseInt(startArray[0]), parseInt(startArray[1]), 0, 0);
+ let endDate = new Date();
+ endDate.setHours(parseInt(endArray[0]), parseInt(endArray[1]), 0, 0);
+ return (((100 - percentDone) / 100) * (endDate - startDate) / (60 * 1000)).toFixed(0); // Convert milliseconds into minutes
+ }
+
+ async setupNotifications(number, remainingTime) {
+ if (!this.isMachineWatched(number)) {
+ let endNotifID = await NotificationsManager.scheduleNotification(
+ i18n.t('proxiwashScreen.notifications.machineFinishedTitle'),
+ i18n.t('proxiwashScreen.notifications.machineFinishedBody', {number: number}),
+ new Date().getTime() + remainingTime * (60 * 1000) // Convert back to milliseconds
+ );
+ let remainderNotifID = undefined;
+ if (remainingTime > remainderNotifTime) {
+ remainderNotifID = await NotificationsManager.scheduleNotification(
+ i18n.t('proxiwashScreen.notifications.machineRunningTitle', {time: remainderNotifTime}),
+ i18n.t('proxiwashScreen.notifications.machineRunningBody', {number: number}),
+ new Date().getTime() + (remainingTime - remainderNotifTime) * (60 * 1000) // Convert back to milliseconds
+ );
+ }
+ let data = this.state.machinesWatched;
+ data.push({machineNumber: number, endNotifID: endNotifID, remainderNotifID: remainderNotifID});
+ this.setState({machinesWatched: data});
+ AsyncStorage.setItem(WATCHED_MACHINES_PREFKEY, JSON.stringify(data));
+ } else
+ this.disableNotification(number);
+ }
+
+ disableNotification(number) {
+ let data = this.state.machinesWatched;
+ if (data.length > 0) {
+ let elem = this.state.machinesWatched.find(function (elem) {
+ return elem.machineNumber === number
+ });
+ let arrayIndex = data.indexOf(elem);
+ NotificationsManager.cancelScheduledNoification(data[arrayIndex].endNotifID);
+ if (data[arrayIndex].remainderNotifID !== undefined)
+ NotificationsManager.cancelScheduledNoification(data[arrayIndex].remainderNotifID);
+ data.splice(arrayIndex, 1);
+ this.setState({machinesWatched: data});
+ AsyncStorage.setItem(WATCHED_MACHINES_PREFKEY, JSON.stringify(data));
+ }
+ }
+
+ isMachineWatched(number) {
+ return this.state.machinesWatched.find(function (elem) {
+ return elem.machineNumber === number
+ }) !== undefined;
+ }
+
+ renderItem(item, section, data) {
+ return (
+
+
+
+
+
+
+
+
+ {section.title === data[0].title ? i18n.t('proxiwashScreen.dryer') : i18n.t('proxiwashScreen.washer')} n°{item.number}
+
+
+ {item.startTime !== '' ? item.startTime + '/' + item.endTime : ''}
+
+
+
+ {item.startTime !== '' ?
+ {
+ this.setupNotifications(item.number, ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent))
+ }}>
+
+ {ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent) + ' ' + i18n.t('proxiwashScreen.min')}
+
+
+
+ : {stateStrings[MACHINE_STATES[item.state]]}
+
+ }
+
+ );
+ }
+
render() {
const nav = this.props.navigation;
+ const data = [
+ {
+ title: i18n.t('proxiwashScreen.dryers'),
+ data: this.state.data.dryers === undefined ? [] : this.state.data.dryers,
+ extraData: this.state
+ },
+ {
+ title: i18n.t('proxiwashScreen.washers'),
+ data: this.state.data.washers === undefined ? [] : this.state.data.washers,
+ extraData: this.state
+ },
+ ];
+ console.log(this.state.machinesWatched);
return (
+ item.number}
+ refreshControl={
+
+ }
+ renderSectionHeader={({section: {title}}) => (
+ {title}
+ )}
+ renderItem={({item, section}) =>
+ this.renderItem(item, section, data)
+ }
+ />
);
}
}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#fff',
- alignItems: 'center',
- justifyContent: 'center',
- },
-});
diff --git a/translations/en.json b/translations/en.json
index f56eb14..c422c51 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -28,5 +28,26 @@
"sortName": "Sort by name",
"sortPrice": "Sort by price",
"listUpdated": "Article list updated!"
+ },
+ "proxiwashScreen": {
+ "dryer": "Dryer",
+ "dryers": "Dryers",
+ "washer": "Washer",
+ "washers": "Washers",
+ "min": "min",
+ "listUpdated": "Machines state updated",
+ "states": {
+ "finished": "FINISHED",
+ "ready": "READY",
+ "running": "RUNNING",
+ "broken": "BROKEN",
+ "error": "ERROR"
+ },
+ "notifications": {
+ "machineFinishedTitle": "Laundry Ready",
+ "machineFinishedBody": "The machine n°{{number}} is finished and your laundry is ready to pickup",
+ "machineRunningTitle": "Laundry running: {{time}} minutes left",
+ "machineRunningBody": "The machine n°{{number}} is still running"
+ }
}
}
diff --git a/translations/fr.json b/translations/fr.json
index 22d2e87..6ca2ca8 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -28,5 +28,26 @@
"sortName": "Trier par nom",
"sortPrice": "Trier par prix",
"listUpdated": "Liste des articles mise à jour !"
+ },
+ "proxiwashScreen": {
+ "dryer": "Sèche Linge",
+ "dryers": "Sèche Linges",
+ "washer": "Lave Linge",
+ "washers": "Lave Linges",
+ "min": "min",
+ "listUpdated": "Etat des machines mis à jour",
+ "states": {
+ "finished": "TERMINE",
+ "ready": "DISPONIBLE",
+ "running": "En COURS",
+ "broken": "HORS SERVICE",
+ "error": "ERREUR"
+ },
+ "notifications": {
+ "machineFinishedTitle": "Linge prêt",
+ "machineFinishedBody": "La machine n°{{number}} est terminée et votre linge est prêt à être récupéré",
+ "machineRunningTitle": "Machine en cours: {{time}} minutes restantes",
+ "machineRunningBody": "La machine n°{{number}} n'est pas encore terminée"
+ }
}
}
diff --git a/utils/NotificationsManager.js b/utils/NotificationsManager.js
new file mode 100644
index 0000000..42d706c
--- /dev/null
+++ b/utils/NotificationsManager.js
@@ -0,0 +1,40 @@
+import * as Permissions from 'expo-permissions';
+import { Notifications } from 'expo';
+
+export default class NotificationsManager {
+
+ static async askPermissions() {
+ const {status: existingStatus} = await Permissions.getAsync(Permissions.NOTIFICATIONS);
+ let finalStatus = existingStatus;
+ if (existingStatus !== 'granted') {
+ const {status} = await Permissions.askAsync(Permissions.NOTIFICATIONS);
+ finalStatus = status;
+ }
+ return finalStatus === 'granted';
+ }
+
+ static async sendNotificationImmediately (title, body) {
+ await NotificationsManager.askPermissions();
+ return await Notifications.presentLocalNotificationAsync({
+ title: title,
+ body: body,
+ });
+ };
+
+ static async scheduleNotification(title, body, time) {
+ await NotificationsManager.askPermissions();
+ return Notifications.scheduleLocalNotificationAsync(
+ {
+ title: title,
+ body: body,
+ },
+ {
+ time: time,
+ },
+ );
+ };
+
+ static async cancelScheduledNoification(notifID) {
+ await Notifications.cancelScheduledNotificationAsync(notifID);
+ }
+}
diff --git a/utils/ThemeManager.js b/utils/ThemeManager.js
index 07b5319..5046007 100644
--- a/utils/ThemeManager.js
+++ b/utils/ThemeManager.js
@@ -1,4 +1,3 @@
-import {DefaultTheme} from 'react-native-paper';
import {AsyncStorage} from 'react-native'
import platform from '../native-base-theme/variables/platform';
import platformDark from '../native-base-theme/variables/platformDark';