Browse Source

Replaced local notifications with push notifications

keplyx 2 years ago
parent
commit
8d4223333f

+ 4
- 0
App.js View File

@@ -13,6 +13,8 @@ import * as Font from 'expo-font';
13 13
 import {clearThemeCache} from 'native-base-shoutem-theme';
14 14
 import AsyncStorageManager from "./utils/AsyncStorageManager";
15 15
 import CustomIntroSlider from "./components/CustomIntroSlider";
16
+import {Notifications} from 'expo';
17
+import NotificationsManager from "./utils/NotificationsManager";
16 18
 
17 19
 type Props = {};
18 20
 
@@ -47,6 +49,8 @@ export default class App extends React.Component<Props, State> {
47 49
         });
48 50
         await AsyncStorageManager.getInstance().loadPreferences();
49 51
         ThemeManager.getInstance().setUpdateThemeCallback(() => this.updateTheme());
52
+        await NotificationsManager.initExpoToken();
53
+        console.log(AsyncStorageManager.getInstance().preferences.expoToken.current);
50 54
         // Only show intro if this is the first time starting the app
51 55
         this.setState({
52 56
             isLoading: false,

+ 8
- 4
components/FetchedDataSectionList.js View File

@@ -17,11 +17,9 @@ type State = {
17 17
     refreshing: boolean,
18 18
     firstLoading: boolean,
19 19
     fetchedData: Object,
20
-    machinesWatched: Array<Object>,
20
+    machinesWatched: Array<string>,
21 21
 };
22 22
 
23
-const minTimeBetweenRefresh = 60;
24
-
25 23
 /**
26 24
  * Class used to create a basic list view using online json data.
27 25
  * Used by inheriting from it and redefining getters.
@@ -36,6 +34,8 @@ export default class FetchedDataSectionList extends React.Component<Props, State
36 34
     refreshTime: number;
37 35
     lastRefresh: Date;
38 36
 
37
+    minTimeBetweenRefresh = 60;
38
+
39 39
     constructor(fetchUrl: string, refreshTime: number) {
40 40
         super();
41 41
         this.webDataManager = new WebDataManager(fetchUrl);
@@ -65,6 +65,10 @@ export default class FetchedDataSectionList extends React.Component<Props, State
65 65
         return ["whoa", "nah"];
66 66
     }
67 67
 
68
+    setMinTimeRefresh(value: number) {
69
+        this.minTimeBetweenRefresh = value;
70
+    }
71
+
68 72
     /**
69 73
      * Register react navigation events on first screen load.
70 74
      * Allows to detect when the screen is focused
@@ -117,7 +121,7 @@ export default class FetchedDataSectionList extends React.Component<Props, State
117 121
     _onRefresh = () => {
118 122
         let canRefresh;
119 123
         if (this.lastRefresh !== undefined)
120
-            canRefresh = (new Date().getTime() - this.lastRefresh.getTime())/1000 > minTimeBetweenRefresh;
124
+            canRefresh = (new Date().getTime() - this.lastRefresh.getTime())/1000 > this.minTimeBetweenRefresh;
121 125
         else
122 126
             canRefresh = true;
123 127
 

+ 1
- 1
screens/HomeScreen.js View File

@@ -11,7 +11,7 @@ import ThemeManager from "../utils/ThemeManager";
11 11
 
12 12
 const ICON_AMICALE = require('../assets/amicale.png');
13 13
 const NAME_AMICALE = 'Amicale INSA Toulouse';
14
-const DATA_URL = "https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/facebook_data.json";
14
+const DATA_URL = "https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/facebook/facebook_data.json";
15 15
 
16 16
 
17 17
 /**

+ 4
- 4
screens/PlanexScreen.js View File

@@ -16,8 +16,8 @@ type Props = {
16 16
 
17 17
 const PLANEX_URL = 'http://planex.insa-toulouse.fr/';
18 18
 
19
-const CUSTOM_CSS_LINK = 'https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/planex/generalCustom.css';
20
-
19
+const CUSTOM_CSS_GENERAL = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/custom_css/planex/customMobile.css';
20
+const CUSTOM_CSS_NIGHTMODE = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/custom_css/planex/customDark.css';
21 21
 /**
22 22
  * Class defining the app's planex screen.
23 23
  * This screen uses a webview to render the planex page
@@ -31,9 +31,9 @@ export default class PlanningScreen extends React.Component<Props> {
31 31
         super();
32 32
         this.customInjectedJS =
33 33
             'document.querySelector(\'head\').innerHTML += \'<meta name="viewport" content="width=device-width, initial-scale=1.0">\';' +
34
-            'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/planex/customMobile.css" type="text/css"/>\';';
34
+            'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="' + CUSTOM_CSS_GENERAL + '" type="text/css"/>\';';
35 35
         if (ThemeManager.getNightMode())
36
-            this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/planex/customDark.css" type="text/css"/>\';';
36
+            this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="' + CUSTOM_CSS_NIGHTMODE + '" type="text/css"/>\';';
37 37
     }
38 38
 
39 39
 

+ 2
- 1
screens/PlanningScreen.js View File

@@ -6,7 +6,7 @@ import i18n from "i18n-js";
6 6
 import {Platform, View} from "react-native";
7 7
 import CustomMaterialIcon from "../components/CustomMaterialIcon";
8 8
 import ThemeManager from "../utils/ThemeManager";
9
-import {Linking} from "expo";
9
+import {Linking, Notifications} from "expo";
10 10
 import BaseContainer from "../components/BaseContainer";
11 11
 
12 12
 type Props = {
@@ -25,6 +25,7 @@ function openWebLink(link) {
25 25
  * Class defining the app's planning screen
26 26
  */
27 27
 export default class PlanningScreen extends React.Component<Props> {
28
+
28 29
     render() {
29 30
         const nav = this.props.navigation;
30 31
         return (

+ 17
- 81
screens/ProxiwashScreen.js View File

@@ -13,9 +13,7 @@ import Touchable from "react-native-platform-touchable";
13 13
 import AsyncStorageManager from "../utils/AsyncStorageManager";
14 14
 import * as Expo from "expo";
15 15
 
16
-const DATA_URL = "https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/washinsa/washinsa.json";
17
-
18
-let reminderNotifTime = 5;
16
+const DATA_URL = "https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json";
19 17
 
20 18
 const MACHINE_STATES = {
21 19
     "TERMINE": "0",
@@ -43,7 +41,7 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
43 41
      * Creates machine state parameters using current theme and translations
44 42
      */
45 43
     constructor() {
46
-        super(DATA_URL, 1000 * 60); // Refresh every minute
44
+        super(DATA_URL, 1000 * 30); // Refresh every half minute
47 45
         let colors = ThemeManager.getCurrentThemeVariables();
48 46
         stateColors[MACHINE_STATES.TERMINE] = colors.proxiwashFinishedColor;
49 47
         stateColors[MACHINE_STATES.DISPONIBLE] = colors.proxiwashReadyColor;
@@ -69,13 +67,15 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
69 67
         stateIcons[MACHINE_STATES.HS] = 'alert-octagram-outline';
70 68
         stateIcons[MACHINE_STATES.ERREUR] = 'alert';
71 69
 
72
-        let dataString = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current;
70
+        // let dataString = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current;
73 71
         this.state = {
74 72
             refreshing: false,
75 73
             firstLoading: true,
76 74
             fetchedData: {},
77
-            machinesWatched: JSON.parse(dataString),
75
+            // machinesWatched: JSON.parse(dataString),
76
+            machinesWatched: [],
78 77
         };
78
+        this.setMinTimeRefresh(30);
79 79
     }
80 80
 
81 81
     /**
@@ -90,14 +90,6 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
90 90
                 vibrate: [0, 250, 250, 250],
91 91
             });
92 92
         }
93
-        // Remove machine from watch list when receiving last notification
94
-        Expo.Notifications.addListener((notification) => {
95
-            if (notification.data !== undefined) {
96
-                if (this.isMachineWatched(notification.data.id) && notification.data.isMachineFinished === true) {
97
-                    this.removeNotificationFromPrefs(this.getMachineIndexInWatchList(notification.data.id));
98
-                }
99
-            }
100
-        });
101 93
     }
102 94
 
103 95
     getHeaderTranslation() {
@@ -140,49 +132,16 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
140 132
      * Another will be send a few minutes before the end, based on the value of reminderNotifTime
141 133
      *
142 134
      * @param machineId The machine's ID
143
-     * @param remainingTime The time remaining for this machine
144 135
      * @returns {Promise<void>}
145 136
      */
146
-    async setupNotifications(machineId: string, remainingTime: number) {
137
+    setupNotifications(machineId: string) {
147 138
         if (!this.isMachineWatched(machineId)) {
148
-            let endNotificationID = await NotificationsManager.scheduleNotification(
149
-                i18n.t('proxiwashScreen.notifications.machineFinishedTitle'),
150
-                i18n.t('proxiwashScreen.notifications.machineFinishedBody', {number: machineId}),
151
-                new Date().getTime() + remainingTime * (60 * 1000), // Convert back to milliseconds
152
-                {id: machineId, isMachineFinished: true},
153
-                'reminders'
154
-            );
155
-            let reminderNotificationID = await ProxiwashScreen.setupReminderNotification(machineId, remainingTime);
156
-            this.saveNotificationToPrefs(machineId, endNotificationID, reminderNotificationID);
139
+            NotificationsManager.setupMachineNotification(machineId, true);
140
+            this.saveNotificationToPrefs(machineId);
157 141
         } else
158 142
             this.disableNotification(machineId);
159 143
     }
160 144
 
161
-    static async setupReminderNotification(machineId: string, remainingTime: number): Promise<string | null> {
162
-        let reminderNotificationID: string | null = null;
163
-        let reminderNotificationTime = ProxiwashScreen.getReminderNotificationTime();
164
-        if (remainingTime > reminderNotificationTime && reminderNotificationTime > 0) {
165
-            reminderNotificationID = await NotificationsManager.scheduleNotification(
166
-                i18n.t('proxiwashScreen.notifications.machineRunningTitle', {time: reminderNotificationTime}),
167
-                i18n.t('proxiwashScreen.notifications.machineRunningBody', {number: machineId}),
168
-                new Date().getTime() + (remainingTime - reminderNotificationTime) * (60 * 1000), // Convert back to milliseconds
169
-                {id: machineId, isMachineFinished: false},
170
-                'reminders'
171
-            );
172
-        }
173
-        return reminderNotificationID;
174
-    }
175
-
176
-    static getReminderNotificationTime(): number {
177
-        let val = AsyncStorageManager.getInstance().preferences.proxiwashNotifications.current;
178
-        if (val !== "never")
179
-            reminderNotifTime = parseInt(val);
180
-        else
181
-            reminderNotifTime = -1;
182
-        return reminderNotifTime;
183
-    }
184
-
185
-
186 145
     /**
187 146
      * Stop scheduled notifications for the machine of the given ID.
188 147
      * This will also remove the notification if it was already shown.
@@ -192,43 +151,22 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
192 151
     disableNotification(machineId: string) {
193 152
         let data = this.state.machinesWatched;
194 153
         if (data.length > 0) {
195
-            let arrayIndex = this.getMachineIndexInWatchList(machineId);
154
+            let arrayIndex = data.indexOf(machineId);
196 155
             if (arrayIndex !== -1) {
197
-                NotificationsManager.cancelScheduledNotification(data[arrayIndex].endNotificationID);
198
-                if (data[arrayIndex].reminderNotificationID !== null)
199
-                    NotificationsManager.cancelScheduledNotification(data[arrayIndex].reminderNotificationID);
156
+                NotificationsManager.setupMachineNotification(machineId, false);
200 157
                 this.removeNotificationFromPrefs(arrayIndex);
201 158
             }
202 159
         }
203 160
     }
204 161
 
205 162
     /**
206
-     * Get the index of the given machine ID in the watchlist array
207
-     *
208
-     * @param machineId
209
-     * @return
210
-     */
211
-    getMachineIndexInWatchList(machineId: string): number {
212
-        let elem = this.state.machinesWatched.find(function (elem) {
213
-            return elem.machineNumber === machineId
214
-        });
215
-        return this.state.machinesWatched.indexOf(elem);
216
-    }
217
-
218
-    /**
219 163
      * Add the given notifications associated to a machine ID to the watchlist, and save the array to the preferences
220 164
      *
221 165
      * @param machineId
222
-     * @param endNotificationID
223
-     * @param reminderNotificationID
224 166
      */
225
-    saveNotificationToPrefs(machineId: string, endNotificationID: string, reminderNotificationID: string | null) {
167
+    saveNotificationToPrefs(machineId: string) {
226 168
         let data = this.state.machinesWatched;
227
-        data.push({
228
-            machineNumber: machineId,
229
-            endNotificationID: endNotificationID,
230
-            reminderNotificationID: reminderNotificationID
231
-        });
169
+        data.push(machineId);
232 170
         this.updateNotificationPrefs(data);
233 171
     }
234 172
 
@@ -250,8 +188,8 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
250 188
      */
251 189
     updateNotificationPrefs(data: Array<Object>) {
252 190
         this.setState({machinesWatched: data});
253
-        let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key;
254
-        AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data));
191
+        // let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key;
192
+        // AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data));
255 193
     }
256 194
 
257 195
     /**
@@ -261,9 +199,7 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
261 199
      * @returns {boolean}
262 200
      */
263 201
     isMachineWatched(machineID: string) {
264
-        return this.state.machinesWatched.find(function (elem) {
265
-            return elem.machineNumber === machineID
266
-        }) !== undefined;
202
+        return this.state.machinesWatched.indexOf(machineID) !== -1;
267 203
     }
268 204
 
269 205
     createDataset(fetchedData: Object) {
@@ -307,7 +243,7 @@ export default class ProxiwashScreen extends FetchedDataSectionList {
307 243
                     text: this.isMachineWatched(item.number) ?
308 244
                         i18n.t("proxiwashScreen.modal.disableNotifications") :
309 245
                         i18n.t("proxiwashScreen.modal.enableNotifications"),
310
-                    onPress: () => this.setupNotifications(item.number, remainingTime)
246
+                    onPress: () => this.setupNotifications(item.number)
311 247
                 },
312 248
                 {
313 249
                     text: i18n.t("proxiwashScreen.modal.cancel")

+ 4
- 2
screens/SelfMenuScreen.js View File

@@ -16,6 +16,8 @@ type Props = {
16 16
 
17 17
 
18 18
 const RU_URL = 'http://m.insa-toulouse.fr/ru.html';
19
+const CUSTOM_CSS_GENERAL = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/custom_css/RU/customGeneral.css';
20
+const CUSTOM_CSS_LIGHT = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/custom_css/RU/customLight.css';
19 21
 
20 22
 /**
21 23
  * Class defining the app's planex screen.
@@ -30,9 +32,9 @@ export default class SelfMenuScreen extends React.Component<Props> {
30 32
         super();
31 33
         this.customInjectedJS =
32 34
             'document.querySelector(\'head\').innerHTML += \'<meta name="viewport" content="width=device-width, initial-scale=1.0">\';' +
33
-            'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/RU/customGeneral.css" type="text/css"/>\';';
35
+            'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="' + CUSTOM_CSS_GENERAL + '" type="text/css"/>\';';
34 36
         if (!ThemeManager.getNightMode())
35
-            this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/RU/customLight.css" type="text/css"/>\';';
37
+            this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="' + CUSTOM_CSS_LIGHT + '" type="text/css"/>\';';
36 38
     }
37 39
 
38 40
     getRefreshButton() {

+ 5
- 0
utils/AsyncStorageManager.js View File

@@ -43,6 +43,11 @@ export default class AsyncStorageManager {
43 43
             key: 'nightMode',
44 44
             default: '0',
45 45
             current: '',
46
+        },
47
+        expoToken: {
48
+            key: 'expoToken',
49
+            default: '',
50
+            current: '',
46 51
         }
47 52
     };
48 53
 

+ 58
- 0
utils/NotificationsManager.js View File

@@ -2,6 +2,9 @@
2 2
 
3 3
 import * as Permissions from 'expo-permissions';
4 4
 import {Notifications} from 'expo';
5
+import AsyncStorageManager from "./AsyncStorageManager";
6
+
7
+const EXPO_TOKEN_SERVER = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/expo_notifications/save_token.php';
5 8
 
6 9
 /**
7 10
  * Static class used to manage notifications sent to the user
@@ -45,10 +48,15 @@ export default class NotificationsManager {
45 48
      * @param body Notification body text
46 49
      * @param time Time at which we should send the notification
47 50
      * @param data Data to send with the notification, used for listeners
51
+     * @param androidChannelID
48 52
      * @returns {Promise<import("react").ReactText>} Notification Id
49 53
      */
50 54
     static async scheduleNotification(title: string, body: string, time: number, data: Object, androidChannelID: string): Promise<string> {
51 55
         await NotificationsManager.askPermissions();
56
+        console.log(time);
57
+        let date = new Date();
58
+        date.setTime(time);
59
+        console.log(date);
52 60
         return Notifications.scheduleLocalNotificationAsync(
53 61
             {
54 62
                 title: title,
@@ -75,4 +83,54 @@ export default class NotificationsManager {
75 83
     static async cancelScheduledNotification(notificationID: number) {
76 84
         await Notifications.cancelScheduledNotificationAsync(notificationID);
77 85
     }
86
+
87
+    /**
88
+     * Save expo token to allow sending notifications to this device.
89
+     * This token is unique for each device and won't change.
90
+     * It only needs to be fetched once, then it will be saved in storage.
91
+     *
92
+     * @return {Promise<void>}
93
+     */
94
+    static async initExpoToken() {
95
+        let token = AsyncStorageManager.getInstance().preferences.expoToken.current;
96
+        if (AsyncStorageManager.getInstance().preferences.expoToken.current === '') {
97
+            let expoToken = await Notifications.getExpoPushTokenAsync();
98
+            // Save token for instant use later on
99
+            AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.expoToken.key, expoToken);
100
+        }
101
+    }
102
+
103
+    /**
104
+     * Ask the server to enable/disable notifications for the specified machine
105
+     *
106
+     * @param machineID
107
+     * @param isEnabled
108
+     */
109
+    static setupMachineNotification(machineID: string, isEnabled: boolean) {
110
+        let token = AsyncStorageManager.getInstance().preferences.expoToken.current;
111
+        if (token === '') {
112
+            throw Error('Expo token not available');
113
+        }
114
+        let data = {
115
+            function: 'setup_machine_notification',
116
+            token: token,
117
+            machine_id: machineID,
118
+            enabled: isEnabled
119
+        };
120
+        fetch(EXPO_TOKEN_SERVER, {
121
+            method: 'POST',
122
+            headers: new Headers({
123
+                Accept: 'application/json',
124
+                'Content-Type': 'application/json',
125
+            }),
126
+            body: JSON.stringify(data) // <-- Post parameters
127
+        })
128
+            .then((response) => response.text())
129
+            .then((responseText) => {
130
+                console.log(responseText);
131
+            })
132
+            .catch((error) => {
133
+                console.log(error);
134
+            });
135
+    }
78 136
 }

Loading…
Cancel
Save