diff --git a/App.js b/App.js index bff963c..e61f1c1 100644 --- a/App.js +++ b/App.js @@ -13,6 +13,8 @@ import * as Font from 'expo-font'; import {clearThemeCache} from 'native-base-shoutem-theme'; import AsyncStorageManager from "./utils/AsyncStorageManager"; import CustomIntroSlider from "./components/CustomIntroSlider"; +import {Notifications} from 'expo'; +import NotificationsManager from "./utils/NotificationsManager"; type Props = {}; @@ -47,6 +49,8 @@ export default class App extends React.Component { }); await AsyncStorageManager.getInstance().loadPreferences(); ThemeManager.getInstance().setUpdateThemeCallback(() => this.updateTheme()); + await NotificationsManager.initExpoToken(); + console.log(AsyncStorageManager.getInstance().preferences.expoToken.current); // Only show intro if this is the first time starting the app this.setState({ isLoading: false, diff --git a/components/FetchedDataSectionList.js b/components/FetchedDataSectionList.js index ab74572..28a99c6 100644 --- a/components/FetchedDataSectionList.js +++ b/components/FetchedDataSectionList.js @@ -17,11 +17,9 @@ type State = { refreshing: boolean, firstLoading: boolean, fetchedData: Object, - machinesWatched: Array, + machinesWatched: Array, }; -const minTimeBetweenRefresh = 60; - /** * Class used to create a basic list view using online json data. * Used by inheriting from it and redefining getters. @@ -36,6 +34,8 @@ export default class FetchedDataSectionList extends React.Component { let canRefresh; if (this.lastRefresh !== undefined) - canRefresh = (new Date().getTime() - this.lastRefresh.getTime())/1000 > minTimeBetweenRefresh; + canRefresh = (new Date().getTime() - this.lastRefresh.getTime())/1000 > this.minTimeBetweenRefresh; else canRefresh = true; diff --git a/screens/HomeScreen.js b/screens/HomeScreen.js index 4b11410..6dedf86 100644 --- a/screens/HomeScreen.js +++ b/screens/HomeScreen.js @@ -11,7 +11,7 @@ import ThemeManager from "../utils/ThemeManager"; const ICON_AMICALE = require('../assets/amicale.png'); const NAME_AMICALE = 'Amicale INSA Toulouse'; -const DATA_URL = "https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/facebook_data.json"; +const DATA_URL = "https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/facebook/facebook_data.json"; /** diff --git a/screens/PlanexScreen.js b/screens/PlanexScreen.js index 29eb6d9..8ca6cb2 100644 --- a/screens/PlanexScreen.js +++ b/screens/PlanexScreen.js @@ -16,8 +16,8 @@ type Props = { const PLANEX_URL = 'http://planex.insa-toulouse.fr/'; -const CUSTOM_CSS_LINK = 'https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/planex/generalCustom.css'; - +const CUSTOM_CSS_GENERAL = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/custom_css/planex/customMobile.css'; +const CUSTOM_CSS_NIGHTMODE = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/custom_css/planex/customDark.css'; /** * Class defining the app's planex screen. * This screen uses a webview to render the planex page @@ -31,9 +31,9 @@ export default class PlanningScreen extends React.Component { super(); this.customInjectedJS = 'document.querySelector(\'head\').innerHTML += \'\';' + - 'document.querySelector(\'head\').innerHTML += \'\';'; + 'document.querySelector(\'head\').innerHTML += \'\';'; if (ThemeManager.getNightMode()) - this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'\';'; + this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'\';'; } diff --git a/screens/PlanningScreen.js b/screens/PlanningScreen.js index 9d70446..7e38d77 100644 --- a/screens/PlanningScreen.js +++ b/screens/PlanningScreen.js @@ -6,7 +6,7 @@ import i18n from "i18n-js"; import {Platform, View} from "react-native"; import CustomMaterialIcon from "../components/CustomMaterialIcon"; import ThemeManager from "../utils/ThemeManager"; -import {Linking} from "expo"; +import {Linking, Notifications} from "expo"; import BaseContainer from "../components/BaseContainer"; type Props = { @@ -25,6 +25,7 @@ function openWebLink(link) { * Class defining the app's planning screen */ export default class PlanningScreen extends React.Component { + render() { const nav = this.props.navigation; return ( diff --git a/screens/ProxiwashScreen.js b/screens/ProxiwashScreen.js index 35fa302..d630203 100644 --- a/screens/ProxiwashScreen.js +++ b/screens/ProxiwashScreen.js @@ -13,9 +13,7 @@ import Touchable from "react-native-platform-touchable"; import AsyncStorageManager from "../utils/AsyncStorageManager"; import * as Expo from "expo"; -const DATA_URL = "https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/washinsa/washinsa.json"; - -let reminderNotifTime = 5; +const DATA_URL = "https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json"; const MACHINE_STATES = { "TERMINE": "0", @@ -43,7 +41,7 @@ export default class ProxiwashScreen extends FetchedDataSectionList { * Creates machine state parameters using current theme and translations */ constructor() { - super(DATA_URL, 1000 * 60); // Refresh every minute + super(DATA_URL, 1000 * 30); // Refresh every half minute let colors = ThemeManager.getCurrentThemeVariables(); stateColors[MACHINE_STATES.TERMINE] = colors.proxiwashFinishedColor; stateColors[MACHINE_STATES.DISPONIBLE] = colors.proxiwashReadyColor; @@ -69,13 +67,15 @@ export default class ProxiwashScreen extends FetchedDataSectionList { stateIcons[MACHINE_STATES.HS] = 'alert-octagram-outline'; stateIcons[MACHINE_STATES.ERREUR] = 'alert'; - let dataString = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current; + // let dataString = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current; this.state = { refreshing: false, firstLoading: true, fetchedData: {}, - machinesWatched: JSON.parse(dataString), + // machinesWatched: JSON.parse(dataString), + machinesWatched: [], }; + this.setMinTimeRefresh(30); } /** @@ -90,14 +90,6 @@ export default class ProxiwashScreen extends FetchedDataSectionList { vibrate: [0, 250, 250, 250], }); } - // Remove machine from watch list when receiving last notification - Expo.Notifications.addListener((notification) => { - if (notification.data !== undefined) { - if (this.isMachineWatched(notification.data.id) && notification.data.isMachineFinished === true) { - this.removeNotificationFromPrefs(this.getMachineIndexInWatchList(notification.data.id)); - } - } - }); } getHeaderTranslation() { @@ -140,49 +132,16 @@ export default class ProxiwashScreen extends FetchedDataSectionList { * Another will be send a few minutes before the end, based on the value of reminderNotifTime * * @param machineId The machine's ID - * @param remainingTime The time remaining for this machine * @returns {Promise} */ - async setupNotifications(machineId: string, remainingTime: number) { + setupNotifications(machineId: string) { if (!this.isMachineWatched(machineId)) { - let endNotificationID = await NotificationsManager.scheduleNotification( - i18n.t('proxiwashScreen.notifications.machineFinishedTitle'), - i18n.t('proxiwashScreen.notifications.machineFinishedBody', {number: machineId}), - new Date().getTime() + remainingTime * (60 * 1000), // Convert back to milliseconds - {id: machineId, isMachineFinished: true}, - 'reminders' - ); - let reminderNotificationID = await ProxiwashScreen.setupReminderNotification(machineId, remainingTime); - this.saveNotificationToPrefs(machineId, endNotificationID, reminderNotificationID); + NotificationsManager.setupMachineNotification(machineId, true); + this.saveNotificationToPrefs(machineId); } else this.disableNotification(machineId); } - static async setupReminderNotification(machineId: string, remainingTime: number): Promise { - let reminderNotificationID: string | null = null; - let reminderNotificationTime = ProxiwashScreen.getReminderNotificationTime(); - if (remainingTime > reminderNotificationTime && reminderNotificationTime > 0) { - reminderNotificationID = await NotificationsManager.scheduleNotification( - i18n.t('proxiwashScreen.notifications.machineRunningTitle', {time: reminderNotificationTime}), - i18n.t('proxiwashScreen.notifications.machineRunningBody', {number: machineId}), - new Date().getTime() + (remainingTime - reminderNotificationTime) * (60 * 1000), // Convert back to milliseconds - {id: machineId, isMachineFinished: false}, - 'reminders' - ); - } - return reminderNotificationID; - } - - static getReminderNotificationTime(): number { - let val = AsyncStorageManager.getInstance().preferences.proxiwashNotifications.current; - if (val !== "never") - reminderNotifTime = parseInt(val); - else - reminderNotifTime = -1; - return reminderNotifTime; - } - - /** * Stop scheduled notifications for the machine of the given ID. * This will also remove the notification if it was already shown. @@ -192,43 +151,22 @@ export default class ProxiwashScreen extends FetchedDataSectionList { disableNotification(machineId: string) { let data = this.state.machinesWatched; if (data.length > 0) { - let arrayIndex = this.getMachineIndexInWatchList(machineId); + let arrayIndex = data.indexOf(machineId); if (arrayIndex !== -1) { - NotificationsManager.cancelScheduledNotification(data[arrayIndex].endNotificationID); - if (data[arrayIndex].reminderNotificationID !== null) - NotificationsManager.cancelScheduledNotification(data[arrayIndex].reminderNotificationID); + NotificationsManager.setupMachineNotification(machineId, false); this.removeNotificationFromPrefs(arrayIndex); } } } - /** - * Get the index of the given machine ID in the watchlist array - * - * @param machineId - * @return - */ - getMachineIndexInWatchList(machineId: string): number { - let elem = this.state.machinesWatched.find(function (elem) { - return elem.machineNumber === machineId - }); - return this.state.machinesWatched.indexOf(elem); - } - /** * Add the given notifications associated to a machine ID to the watchlist, and save the array to the preferences * * @param machineId - * @param endNotificationID - * @param reminderNotificationID */ - saveNotificationToPrefs(machineId: string, endNotificationID: string, reminderNotificationID: string | null) { + saveNotificationToPrefs(machineId: string) { let data = this.state.machinesWatched; - data.push({ - machineNumber: machineId, - endNotificationID: endNotificationID, - reminderNotificationID: reminderNotificationID - }); + data.push(machineId); this.updateNotificationPrefs(data); } @@ -250,8 +188,8 @@ export default class ProxiwashScreen extends FetchedDataSectionList { */ updateNotificationPrefs(data: Array) { this.setState({machinesWatched: data}); - let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key; - AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data)); + // let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key; + // AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data)); } /** @@ -261,9 +199,7 @@ export default class ProxiwashScreen extends FetchedDataSectionList { * @returns {boolean} */ isMachineWatched(machineID: string) { - return this.state.machinesWatched.find(function (elem) { - return elem.machineNumber === machineID - }) !== undefined; + return this.state.machinesWatched.indexOf(machineID) !== -1; } createDataset(fetchedData: Object) { @@ -307,7 +243,7 @@ export default class ProxiwashScreen extends FetchedDataSectionList { text: this.isMachineWatched(item.number) ? i18n.t("proxiwashScreen.modal.disableNotifications") : i18n.t("proxiwashScreen.modal.enableNotifications"), - onPress: () => this.setupNotifications(item.number, remainingTime) + onPress: () => this.setupNotifications(item.number) }, { text: i18n.t("proxiwashScreen.modal.cancel") diff --git a/screens/SelfMenuScreen.js b/screens/SelfMenuScreen.js index 7a6f666..d2d7db4 100644 --- a/screens/SelfMenuScreen.js +++ b/screens/SelfMenuScreen.js @@ -16,6 +16,8 @@ type Props = { const RU_URL = 'http://m.insa-toulouse.fr/ru.html'; +const CUSTOM_CSS_GENERAL = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/custom_css/RU/customGeneral.css'; +const CUSTOM_CSS_LIGHT = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/custom_css/RU/customLight.css'; /** * Class defining the app's planex screen. @@ -30,9 +32,9 @@ export default class SelfMenuScreen extends React.Component { super(); this.customInjectedJS = 'document.querySelector(\'head\').innerHTML += \'\';' + - 'document.querySelector(\'head\').innerHTML += \'\';'; + 'document.querySelector(\'head\').innerHTML += \'\';'; if (!ThemeManager.getNightMode()) - this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'\';'; + this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'\';'; } getRefreshButton() { diff --git a/utils/AsyncStorageManager.js b/utils/AsyncStorageManager.js index aff8548..ce3dfe4 100644 --- a/utils/AsyncStorageManager.js +++ b/utils/AsyncStorageManager.js @@ -43,6 +43,11 @@ export default class AsyncStorageManager { key: 'nightMode', default: '0', current: '', + }, + expoToken: { + key: 'expoToken', + default: '', + current: '', } }; diff --git a/utils/NotificationsManager.js b/utils/NotificationsManager.js index 036c744..7ae112f 100644 --- a/utils/NotificationsManager.js +++ b/utils/NotificationsManager.js @@ -2,6 +2,9 @@ import * as Permissions from 'expo-permissions'; import {Notifications} from 'expo'; +import AsyncStorageManager from "./AsyncStorageManager"; + +const EXPO_TOKEN_SERVER = 'https://srv-falcon.etud.insa-toulouse.fr/~amicale_app/expo_notifications/save_token.php'; /** * Static class used to manage notifications sent to the user @@ -45,10 +48,15 @@ export default class NotificationsManager { * @param body Notification body text * @param time Time at which we should send the notification * @param data Data to send with the notification, used for listeners + * @param androidChannelID * @returns {Promise} Notification Id */ static async scheduleNotification(title: string, body: string, time: number, data: Object, androidChannelID: string): Promise { await NotificationsManager.askPermissions(); + console.log(time); + let date = new Date(); + date.setTime(time); + console.log(date); return Notifications.scheduleLocalNotificationAsync( { title: title, @@ -75,4 +83,54 @@ export default class NotificationsManager { static async cancelScheduledNotification(notificationID: number) { await Notifications.cancelScheduledNotificationAsync(notificationID); } + + /** + * Save expo token to allow sending notifications to this device. + * This token is unique for each device and won't change. + * It only needs to be fetched once, then it will be saved in storage. + * + * @return {Promise} + */ + static async initExpoToken() { + let token = AsyncStorageManager.getInstance().preferences.expoToken.current; + if (AsyncStorageManager.getInstance().preferences.expoToken.current === '') { + let expoToken = await Notifications.getExpoPushTokenAsync(); + // Save token for instant use later on + AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.expoToken.key, expoToken); + } + } + + /** + * Ask the server to enable/disable notifications for the specified machine + * + * @param machineID + * @param isEnabled + */ + static setupMachineNotification(machineID: string, isEnabled: boolean) { + let token = AsyncStorageManager.getInstance().preferences.expoToken.current; + if (token === '') { + throw Error('Expo token not available'); + } + let data = { + function: 'setup_machine_notification', + token: token, + machine_id: machineID, + enabled: isEnabled + }; + fetch(EXPO_TOKEN_SERVER, { + method: 'POST', + headers: new Headers({ + Accept: 'application/json', + 'Content-Type': 'application/json', + }), + body: JSON.stringify(data) // <-- Post parameters + }) + .then((response) => response.text()) + .then((responseText) => { + console.log(responseText); + }) + .catch((error) => { + console.log(error); + }); + } }