Browse Source

Added notification support for proxiwash

keplyx 2 years ago
parent
commit
be67c70a95

+ 90
- 0
dataProxiwash.json View File

@@ -0,0 +1,90 @@
1
+{
2
+  "dryers": [
3
+    {
4
+      "number": "1",
5
+      "state": "TERMINE",
6
+      "startTime": "",
7
+      "endTime": "",
8
+      "donePercent": ""
9
+    },
10
+    {
11
+      "number": "2",
12
+      "state": "DISPONIBLE",
13
+      "startTime": "",
14
+      "endTime": "",
15
+      "donePercent": ""
16
+    },
17
+    {
18
+      "number": "3",
19
+      "state": "FONCTIONNE",
20
+      "startTime": "10:16",
21
+      "endTime": "10:50",
22
+      "donePercent": "36.5"
23
+    }
24
+  ],
25
+  "washers": [
26
+    {
27
+      "number": "4",
28
+      "state": "DISPONIBLE",
29
+      "startTime": "",
30
+      "endTime": "",
31
+      "donePercent": ""
32
+    },
33
+    {
34
+      "number": "5",
35
+      "state": "DISPONIBLE",
36
+      "startTime": "",
37
+      "endTime": "",
38
+      "donePercent": ""
39
+    },
40
+    {
41
+      "number": "6",
42
+      "state": "DISPONIBLE",
43
+      "startTime": "",
44
+      "endTime": "",
45
+      "donePercent": ""
46
+    },
47
+    {
48
+      "number": "7",
49
+      "state": "DISPONIBLE",
50
+      "startTime": "",
51
+      "endTime": "",
52
+      "donePercent": ""
53
+    },
54
+    {
55
+      "number": "8",
56
+      "state": "DISPONIBLE",
57
+      "startTime": "",
58
+      "endTime": "",
59
+      "donePercent": ""
60
+    },
61
+    {
62
+      "number": "9",
63
+      "state": "FONCTIONNE",
64
+      "startTime": "09:46",
65
+      "endTime": "10:31",
66
+      "donePercent": "93.9"
67
+    },
68
+    {
69
+      "number": "10",
70
+      "state": "DISPONIBLE",
71
+      "startTime": "",
72
+      "endTime": "",
73
+      "donePercent": ""
74
+    },
75
+    {
76
+      "number": "11",
77
+      "state": "HS",
78
+      "startTime": "",
79
+      "endTime": "",
80
+      "donePercent": ""
81
+    },
82
+    {
83
+      "number": "12",
84
+      "state": "DISPONIBLE",
85
+      "startTime": "",
86
+      "endTime": "",
87
+      "donePercent": ""
88
+    }
89
+  ]
90
+}

+ 3
- 2
package.json View File

@@ -12,6 +12,7 @@
12 12
     "expo": "^33.0.0",
13 13
     "expo-font": "^5.0.1",
14 14
     "expo-localization": "^5.0.1",
15
+    "expo-permissions": "^5.0.1",
15 16
     "i18n-js": "^3.3.0",
16 17
     "i18next": "latest",
17 18
     "native-base": "latest",
@@ -20,13 +21,13 @@
20 21
     "react-i18next": "latest",
21 22
     "react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
22 23
     "react-native-paper": "latest",
24
+    "react-native-platform-touchable": "latest",
23 25
     "react-native-settings-page": "latest",
24 26
     "react-native-status-bar-height": "latest",
25 27
     "react-native-web": "^0.11.4",
26 28
     "react-native-week-view": "latest",
27 29
     "react-navigation": "latest",
28
-    "react-navigation-material-bottom-tabs": "latest",
29
-    "react-native-platform-touchable": "latest"
30
+    "react-navigation-material-bottom-tabs": "latest"
30 31
   },
31 32
   "devDependencies": {
32 33
     "babel-preset-expo": "^5.1.1",

+ 2
- 2
screens/HomeScreen.js View File

@@ -2,7 +2,7 @@ import React from 'react';
2 2
 import {Container, Content, Text, Button, Icon} from 'native-base';
3 3
 import CustomHeader from '../components/CustomHeader';
4 4
 import i18n from "i18n-js";
5
-
5
+import NotificationsManager from '../utils/NotificationsManager'
6 6
 import { Notifications } from 'expo';
7 7
 
8 8
 
@@ -13,7 +13,7 @@ export default class HomeScreen extends React.Component {
13 13
             <Container>
14 14
                 <CustomHeader navigation={nav} title={i18n.t('screens.home')}/>
15 15
                 <Content padder>
16
-                    <Button>
16
+                    <Button onPress={() => NotificationsManager.sendNotificationImmediately('coucou', 'whoa')}>
17 17
                         <Icon
18 18
                             active
19 19
                             name={'bell-ring'}

+ 38
- 41
screens/Proximo/ProximoMainScreen.js View File

@@ -54,7 +54,6 @@ export default class ProximoMainScreen extends React.Component {
54 54
                 this.setState({data: undefined});
55 55
         } catch (error) {
56 56
             console.error(error);
57
-            return undefined;
58 57
         }
59 58
     }
60 59
 
@@ -81,46 +80,44 @@ export default class ProximoMainScreen extends React.Component {
81 80
         return (
82 81
             <Container>
83 82
                 <CustomHeader navigation={nav} title={'Proximo'}/>
84
-                <Content>
85
-                    <FlatList
86
-                        data={this.state.data}
87
-                        extraData={this.state}
88
-                        keyExtractor={(item, index) => item.type}
89
-                        refreshControl={
90
-                            <RefreshControl
91
-                                refreshing={this.state.refreshing}
92
-                                onRefresh={this._onRefresh}
93
-                            />
94
-                        }
95
-                        style={{minHeight: 300, width: '100%'}}
96
-                        renderItem={({item}) =>
97
-                            <ListItem
98
-                                button
99
-                                thumbnail
100
-                                onPress={() => {
101
-                                    nav.navigate('ProximoListScreen', item);
102
-                                }}
103
-                            >
104
-                                <Left>
105
-                                    <Icon name={typesIcons[item.type] ? typesIcons[item.type] : typesIcons.Default}
106
-                                          type={'MaterialCommunityIcons'}
107
-                                          style={styles.icon}/>
108
-                                </Left>
109
-                                <Body>
110
-                                    <Text>
111
-                                        {item.type}
112
-                                    </Text>
113
-                                    <Badge><Text>
114
-                                        {item.data.length} {item.data.length > 1 ? i18n.t('proximoScreen.articles') : i18n.t('proximoScreen.article')}
115
-                                    </Text></Badge>
116
-                                </Body>
117
-                                <Right>
118
-                                    <Icon name="chevron-right"
119
-                                          type={'MaterialCommunityIcons'}/>
120
-                                </Right>
121
-                            </ListItem>}
122
-                    />
123
-                </Content>
83
+                <FlatList
84
+                    data={this.state.data}
85
+                    extraData={this.state}
86
+                    keyExtractor={(item, index) => item.type}
87
+                    refreshControl={
88
+                        <RefreshControl
89
+                            refreshing={this.state.refreshing}
90
+                            onRefresh={this._onRefresh}
91
+                        />
92
+                    }
93
+                    style={{minHeight: 300, width: '100%'}}
94
+                    renderItem={({item}) =>
95
+                        <ListItem
96
+                            button
97
+                            thumbnail
98
+                            onPress={() => {
99
+                                nav.navigate('ProximoListScreen', item);
100
+                            }}
101
+                        >
102
+                            <Left>
103
+                                <Icon name={typesIcons[item.type] ? typesIcons[item.type] : typesIcons.Default}
104
+                                      type={'MaterialCommunityIcons'}
105
+                                      style={styles.icon}/>
106
+                            </Left>
107
+                            <Body>
108
+                                <Text>
109
+                                    {item.type}
110
+                                </Text>
111
+                                <Badge><Text>
112
+                                    {item.data.length} {item.data.length > 1 ? i18n.t('proximoScreen.articles') : i18n.t('proximoScreen.article')}
113
+                                </Text></Badge>
114
+                            </Body>
115
+                            <Right>
116
+                                <Icon name="chevron-right"
117
+                                      type={'MaterialCommunityIcons'}/>
118
+                            </Right>
119
+                        </ListItem>}
120
+                />
124 121
             </Container>
125 122
         );
126 123
     }

+ 227
- 11
screens/ProxiwashScreen.js View File

@@ -1,24 +1,240 @@
1 1
 import React from 'react';
2
-import {StyleSheet, View} from 'react-native';
3
-import {Container, Text} from 'native-base';
2
+import {SectionList, RefreshControl, StyleSheet, View, ScrollView} from 'react-native';
3
+import {Badge, Body, Container, Content, Icon, Left, ListItem, Right, Text, Toast, H2, Button} from 'native-base';
4 4
 import CustomHeader from "../components/CustomHeader";
5
+import NotificationsManager from '../utils/NotificationsManager';
6
+import i18n from "i18n-js";
7
+import {AsyncStorage} from 'react-native'
8
+
9
+const DATA_URL = "https://etud.insa-toulouse.fr/~vergnet/appli-amicale/dataProxiwash.json";
10
+const WATCHED_MACHINES_PREFKEY = "proxiwash.watchedMachines";
11
+
12
+const remainderNotifTime = 5;
13
+
14
+const MACHINE_STATES = {
15
+    TERMINE: "0",
16
+    DISPONIBLE: "1",
17
+    FONCTIONNE: "2",
18
+    HS: "3",
19
+    ERREUR: "4"
20
+};
21
+
22
+let stateStrings = {};
23
+
24
+let stateColors = {};
25
+
5 26
 
6 27
 export default class ProxiwashScreen extends React.Component {
28
+
29
+    constructor(props) {
30
+        super(props);
31
+        stateColors[MACHINE_STATES.TERMINE] = 'rgba(54,165,22,0.4)';
32
+        stateColors[MACHINE_STATES.DISPONIBLE] = '#ffffff';
33
+        stateColors[MACHINE_STATES.FONCTIONNE] = 'rgba(241,237,41,0.4)';
34
+        stateColors[MACHINE_STATES.HS] = '#a2a2a2';
35
+        stateColors[MACHINE_STATES.ERREUR] = 'rgba(204,7,0,0.4)';
36
+
37
+        stateStrings[MACHINE_STATES.TERMINE] = i18n.t('proxiwashScreen.states.finished');
38
+        stateStrings[MACHINE_STATES.DISPONIBLE] = i18n.t('proxiwashScreen.states.ready');
39
+        stateStrings[MACHINE_STATES.FONCTIONNE] = i18n.t('proxiwashScreen.states.running');
40
+        stateStrings[MACHINE_STATES.HS] = i18n.t('proxiwashScreen.states.broken');
41
+        stateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.states.error');
42
+        this.state = {
43
+            refreshing: false,
44
+            data: {},
45
+            machinesWatched: [],
46
+        };
47
+    }
48
+
49
+    async readData() {
50
+        try {
51
+            let response = await fetch(DATA_URL);
52
+            let responseJson = await response.json();
53
+            // This prevents end notifications from showing
54
+            // let watchList = this.state.machinesWatched;
55
+            // for (let i = 0; i < watchList.length; i++) {
56
+            //     if (responseJson[MACHINE_STATES[watchList[i].machineNumber.state]] !== MACHINE_STATES.FONCTIONNE)
57
+            //         this.disableNotification(watchList[i].machineNumber);
58
+            // }
59
+            this.setState({
60
+                data: responseJson
61
+            });
62
+        } catch (error) {
63
+            console.error(error);
64
+        }
65
+    }
66
+
67
+    async componentWillMount() {
68
+        let dataString = await AsyncStorage.getItem(WATCHED_MACHINES_PREFKEY);
69
+        if (dataString === null)
70
+            dataString = '[]';
71
+        this.setState({machinesWatched: JSON.parse(dataString)});
72
+    }
73
+
74
+    componentDidMount() {
75
+        this._onRefresh();
76
+    }
77
+
78
+    _onRefresh = () => {
79
+        this.setState({refreshing: true});
80
+        this.readData().then(() => {
81
+            this.setState({refreshing: false});
82
+            Toast.show({
83
+                text: i18n.t('proxiwashScreen.listUpdated'),
84
+                buttonText: 'OK',
85
+                type: "success",
86
+                duration: 2000
87
+            })
88
+        });
89
+    };
90
+
91
+    static getRemainingTime(startString, endString, percentDone) {
92
+        let startArray = startString.split(':');
93
+        let endArray = endString.split(':');
94
+        let startDate = new Date();
95
+        startDate.setHours(parseInt(startArray[0]), parseInt(startArray[1]), 0, 0);
96
+        let endDate = new Date();
97
+        endDate.setHours(parseInt(endArray[0]), parseInt(endArray[1]), 0, 0);
98
+        return (((100 - percentDone) / 100) * (endDate - startDate) / (60 * 1000)).toFixed(0); // Convert milliseconds into minutes
99
+    }
100
+
101
+    async setupNotifications(number, remainingTime) {
102
+        if (!this.isMachineWatched(number)) {
103
+            let endNotifID = await NotificationsManager.scheduleNotification(
104
+                i18n.t('proxiwashScreen.notifications.machineFinishedTitle'),
105
+                i18n.t('proxiwashScreen.notifications.machineFinishedBody', {number: number}),
106
+                new Date().getTime() + remainingTime * (60 * 1000) // Convert back to milliseconds
107
+            );
108
+            let remainderNotifID = undefined;
109
+            if (remainingTime > remainderNotifTime) {
110
+                remainderNotifID = await NotificationsManager.scheduleNotification(
111
+                    i18n.t('proxiwashScreen.notifications.machineRunningTitle', {time: remainderNotifTime}),
112
+                    i18n.t('proxiwashScreen.notifications.machineRunningBody', {number: number}),
113
+                    new Date().getTime() + (remainingTime - remainderNotifTime) * (60 * 1000) // Convert back to milliseconds
114
+                );
115
+            }
116
+            let data = this.state.machinesWatched;
117
+            data.push({machineNumber: number, endNotifID: endNotifID, remainderNotifID: remainderNotifID});
118
+            this.setState({machinesWatched: data});
119
+            AsyncStorage.setItem(WATCHED_MACHINES_PREFKEY, JSON.stringify(data));
120
+        } else
121
+            this.disableNotification(number);
122
+    }
123
+
124
+    disableNotification(number) {
125
+        let data = this.state.machinesWatched;
126
+        if (data.length > 0) {
127
+            let elem = this.state.machinesWatched.find(function (elem) {
128
+                return elem.machineNumber === number
129
+            });
130
+            let arrayIndex = data.indexOf(elem);
131
+            NotificationsManager.cancelScheduledNoification(data[arrayIndex].endNotifID);
132
+            if (data[arrayIndex].remainderNotifID !== undefined)
133
+                NotificationsManager.cancelScheduledNoification(data[arrayIndex].remainderNotifID);
134
+            data.splice(arrayIndex, 1);
135
+            this.setState({machinesWatched: data});
136
+            AsyncStorage.setItem(WATCHED_MACHINES_PREFKEY, JSON.stringify(data));
137
+        }
138
+    }
139
+
140
+    isMachineWatched(number) {
141
+        return this.state.machinesWatched.find(function (elem) {
142
+            return elem.machineNumber === number
143
+        }) !== undefined;
144
+    }
145
+
146
+    renderItem(item, section, data) {
147
+        return (
148
+            <ListItem
149
+                thumbnail
150
+                style={{
151
+                    marginLeft: 0,
152
+                    backgroundColor: stateColors[MACHINE_STATES[item.state]]
153
+                }}
154
+            >
155
+                <View style={{
156
+                    height: '100%',
157
+                    position: 'absolute',
158
+                    alignSelf: 'flex-end',
159
+                    right: 0,
160
+                    width: item.donePercent !== '' ? (100 - parseInt(item.donePercent)).toString() + '%' : 0,
161
+                    backgroundColor: '#fff'
162
+                }}>
163
+                </View>
164
+                <Left>
165
+                    <Icon name={section.title === data[0].title ? 'tumble-dryer' : 'washing-machine'}
166
+                          type={'MaterialCommunityIcons'}
167
+                          style={{fontSize: 30, width: 30}}
168
+                    />
169
+                </Left>
170
+                <Body>
171
+                    <Text>
172
+                        {section.title === data[0].title ? i18n.t('proxiwashScreen.dryer') : i18n.t('proxiwashScreen.washer')} n°{item.number}
173
+                    </Text>
174
+                    <Text note>
175
+                        {item.startTime !== '' ? item.startTime + '/' + item.endTime : ''}
176
+                    </Text>
177
+                </Body>
178
+                <Right>
179
+                    {item.startTime !== '' ?
180
+                        <Button
181
+                            style={this.isMachineWatched(item.number) ?
182
+                                {backgroundColor: '#ba7c1f'} : {}}
183
+                            onPress={() => {
184
+                                this.setupNotifications(item.number, ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent))
185
+                            }}>
186
+                            <Text>
187
+                                {ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent) + ' ' + i18n.t('proxiwashScreen.min')}
188
+                            </Text>
189
+                            <Icon name={this.isMachineWatched(item.number) ? 'bell-ring' : 'bell'}
190
+                                  type={'MaterialCommunityIcons'}
191
+                                  style={{fontSize: 30, width: 30}}
192
+                            />
193
+                        </Button>
194
+                        : <Text style={MACHINE_STATES[item.state] === MACHINE_STATES.TERMINE ?
195
+                            {fontWeight: 'bold'} : {}}
196
+                        >{stateStrings[MACHINE_STATES[item.state]]}</Text>
197
+
198
+                    }
199
+                </Right>
200
+            </ListItem>);
201
+    }
202
+
7 203
     render() {
8 204
         const nav = this.props.navigation;
205
+        const data = [
206
+            {
207
+                title: i18n.t('proxiwashScreen.dryers'),
208
+                data: this.state.data.dryers === undefined ? [] : this.state.data.dryers,
209
+                extraData: this.state
210
+            },
211
+            {
212
+                title: i18n.t('proxiwashScreen.washers'),
213
+                data: this.state.data.washers === undefined ? [] : this.state.data.washers,
214
+                extraData: this.state
215
+            },
216
+        ];
217
+        console.log(this.state.machinesWatched);
9 218
         return (
10 219
             <Container>
11 220
                 <CustomHeader navigation={nav} title={'Proxiwash'}/>
221
+                <SectionList
222
+                    sections={data}
223
+                    keyExtractor={(item) => item.number}
224
+                    refreshControl={
225
+                        <RefreshControl
226
+                            refreshing={this.state.refreshing}
227
+                            onRefresh={this._onRefresh}
228
+                        />
229
+                    }
230
+                    renderSectionHeader={({section: {title}}) => (
231
+                        <H2 style={{textAlign: 'center', paddingVertical: 10}}>{title}</H2>
232
+                    )}
233
+                    renderItem={({item, section}) =>
234
+                        this.renderItem(item, section, data)
235
+                    }
236
+                />
12 237
             </Container>
13 238
         );
14 239
     }
15 240
 }
16
-
17
-const styles = StyleSheet.create({
18
-    container: {
19
-        flex: 1,
20
-        backgroundColor: '#fff',
21
-        alignItems: 'center',
22
-        justifyContent: 'center',
23
-    },
24
-});

+ 21
- 0
translations/en.json View File

@@ -28,5 +28,26 @@
28 28
     "sortName": "Sort by name",
29 29
     "sortPrice": "Sort by price",
30 30
     "listUpdated": "Article list updated!"
31
+  },
32
+  "proxiwashScreen": {
33
+    "dryer": "Dryer",
34
+    "dryers": "Dryers",
35
+    "washer": "Washer",
36
+    "washers": "Washers",
37
+    "min": "min",
38
+    "listUpdated": "Machines state updated",
39
+    "states": {
40
+      "finished": "FINISHED",
41
+      "ready": "READY",
42
+      "running": "RUNNING",
43
+      "broken": "BROKEN",
44
+      "error": "ERROR"
45
+    },
46
+    "notifications": {
47
+      "machineFinishedTitle": "Laundry Ready",
48
+      "machineFinishedBody": "The machine n°{{number}} is finished and your laundry is ready to pickup",
49
+      "machineRunningTitle": "Laundry running: {{time}} minutes left",
50
+      "machineRunningBody": "The machine n°{{number}} is still running"
51
+    }
31 52
   }
32 53
 }

+ 21
- 0
translations/fr.json View File

@@ -28,5 +28,26 @@
28 28
     "sortName": "Trier par nom",
29 29
     "sortPrice": "Trier par prix",
30 30
     "listUpdated": "Liste des articles mise à jour !"
31
+  },
32
+  "proxiwashScreen": {
33
+    "dryer": "Sèche Linge",
34
+    "dryers": "Sèche Linges",
35
+    "washer": "Lave Linge",
36
+    "washers": "Lave Linges",
37
+    "min": "min",
38
+    "listUpdated": "Etat des machines mis à jour",
39
+    "states": {
40
+      "finished": "TERMINE",
41
+      "ready": "DISPONIBLE",
42
+      "running": "En COURS",
43
+      "broken": "HORS SERVICE",
44
+      "error": "ERREUR"
45
+    },
46
+    "notifications": {
47
+      "machineFinishedTitle": "Linge prêt",
48
+      "machineFinishedBody": "La machine n°{{number}} est terminée et votre linge est prêt à être récupéré",
49
+      "machineRunningTitle": "Machine en cours: {{time}} minutes restantes",
50
+      "machineRunningBody": "La machine n°{{number}} n'est pas encore terminée"
51
+    }
31 52
   }
32 53
 }

+ 40
- 0
utils/NotificationsManager.js View File

@@ -0,0 +1,40 @@
1
+import * as Permissions from 'expo-permissions';
2
+import { Notifications } from 'expo';
3
+
4
+export default class NotificationsManager {
5
+
6
+    static async askPermissions() {
7
+        const {status: existingStatus} = await Permissions.getAsync(Permissions.NOTIFICATIONS);
8
+        let finalStatus = existingStatus;
9
+        if (existingStatus !== 'granted') {
10
+            const {status} = await Permissions.askAsync(Permissions.NOTIFICATIONS);
11
+            finalStatus = status;
12
+        }
13
+        return finalStatus === 'granted';
14
+    }
15
+
16
+    static async sendNotificationImmediately (title, body) {
17
+        await NotificationsManager.askPermissions();
18
+        return await Notifications.presentLocalNotificationAsync({
19
+            title: title,
20
+            body: body,
21
+        });
22
+    };
23
+
24
+    static async scheduleNotification(title, body, time) {
25
+        await NotificationsManager.askPermissions();
26
+        return Notifications.scheduleLocalNotificationAsync(
27
+            {
28
+                title: title,
29
+                body: body,
30
+            },
31
+            {
32
+                time: time,
33
+            },
34
+        );
35
+    };
36
+
37
+    static async cancelScheduledNoification(notifID) {
38
+        await Notifications.cancelScheduledNotificationAsync(notifID);
39
+    }
40
+}

+ 0
- 1
utils/ThemeManager.js View File

@@ -1,4 +1,3 @@
1
-import {DefaultTheme} from 'react-native-paper';
2 1
 import {AsyncStorage} from 'react-native'
3 2
 import platform from '../native-base-theme/variables/platform';
4 3
 import platformDark from '../native-base-theme/variables/platformDark';

Loading…
Cancel
Save