From 087331258af331946598a140b4d19e57b113ff12 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Thu, 30 Apr 2020 14:04:31 +0200 Subject: [PATCH] Added local notifications on android --- .gitignore | 1 - App.js | 2 - android/app/src/main/AndroidManifest.xml | 31 +++- android/app/src/release/AndroidManifest.xml | 4 +- package.json | 1 + src/managers/AsyncStorageManager.js | 5 + src/screens/Other/SettingsScreen.js | 5 - src/screens/Proxiwash/ProxiwashScreen.js | 186 +++++++++++--------- src/utils/Notifications.js | 121 +++---------- 9 files changed, 171 insertions(+), 185 deletions(-) diff --git a/.gitignore b/.gitignore index a33277c..59a0b56 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ web-build/ web-report/ /.expo-shared/ /package-lock.json -/passwords.json !/.idea/ /.idea/* diff --git a/App.js b/App.js index faa6fbc..4923f08 100644 --- a/App.js +++ b/App.js @@ -9,7 +9,6 @@ import type {CustomTheme} from "./src/managers/ThemeManager"; import ThemeManager from './src/managers/ThemeManager'; import {NavigationContainer} from '@react-navigation/native'; import MainNavigator from './src/navigation/MainNavigator'; -import {initExpoToken} from "./src/utils/Notifications"; import {Provider as PaperProvider} from 'react-native-paper'; import AprilFoolsManager from "./src/managers/AprilFoolsManager"; import Update from "./src/constants/Update"; @@ -145,7 +144,6 @@ export default class App extends React.Component { */ loadAssetsAsync = async () => { await this.storageManager.loadPreferences(); - await initExpoToken(); try { await ConnectionManager.getInstance().recoverLogin(); } catch (e) { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fccc6b8..cff76b4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,9 @@ - + + + @@ -14,6 +16,33 @@ android:theme="@style/AppTheme" android:usesCleartextTraffic="true" > + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/release/AndroidManifest.xml b/android/app/src/release/AndroidManifest.xml index 718ef28..0ea1411 100644 --- a/android/app/src/release/AndroidManifest.xml +++ b/android/app/src/release/AndroidManifest.xml @@ -3,7 +3,9 @@ + + @@ -21,5 +23,5 @@ - + diff --git a/package.json b/package.json index 477c4f8..f3f11b2 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "react-native-modalize": "^1.3.6", "react-native-paper": "^3.8.0", "react-native-permissions": "^2.1.3", + "react-native-push-notification": "^3.3.0", "react-native-reanimated": "~1.7.0", "react-native-render-html": "^4.1.2", "react-native-safe-area-context": "0.7.3", diff --git a/src/managers/AsyncStorageManager.js b/src/managers/AsyncStorageManager.js index 99a50b9..82bb56f 100644 --- a/src/managers/AsyncStorageManager.js +++ b/src/managers/AsyncStorageManager.js @@ -69,6 +69,11 @@ export default class AsyncStorageManager { default: '1', current: '', }, + proxiwashWatchedMachines: { + key: 'proxiwashWatchedMachines', + default: '[]', + current: '', + }, planexShowBanner: { key: 'planexShowBanner', default: '1', diff --git a/src/screens/Other/SettingsScreen.js b/src/screens/Other/SettingsScreen.js index fde6a9f..aca3620 100644 --- a/src/screens/Other/SettingsScreen.js +++ b/src/screens/Other/SettingsScreen.js @@ -5,7 +5,6 @@ import {ScrollView} from "react-native"; import ThemeManager from '../../managers/ThemeManager'; import i18n from "i18n-js"; import AsyncStorageManager from "../../managers/AsyncStorageManager"; -import {setMachineReminderNotificationTime} from "../../utils/Notifications"; import {Card, List, Switch, ToggleButton} from 'react-native-paper'; import {Appearance} from "react-native-appearance"; import AnimatedAccordion from "../../components/Animations/AnimatedAccordion"; @@ -49,10 +48,6 @@ export default class SettingsScreen extends React.Component { this.setState({ proxiwashNotifPickerSelected: value }); - let intVal = 0; - if (value !== 'never') - intVal = parseInt(value); - setMachineReminderNotificationTime(intVal); } }; diff --git a/src/screens/Proxiwash/ProxiwashScreen.js b/src/screens/Proxiwash/ProxiwashScreen.js index 8771f1a..e131bee 100644 --- a/src/screens/Proxiwash/ProxiwashScreen.js +++ b/src/screens/Proxiwash/ProxiwashScreen.js @@ -6,7 +6,6 @@ import i18n from "i18n-js"; import WebSectionList from "../../components/Screens/WebSectionList"; import * as Notifications from "../../utils/Notifications"; import AsyncStorageManager from "../../managers/AsyncStorageManager"; -// import * as Expo from "expo"; import {Avatar, Banner, Button, Card, Text, withTheme} from 'react-native-paper'; import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem"; import ProxiwashConstants from "../../constants/ProxiwashConstants"; @@ -15,6 +14,11 @@ 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"; + +const PushNotification = require("react-native-push-notification"); const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json"; @@ -23,17 +27,25 @@ let modalStateStrings = {}; const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds const LIST_ITEM_HEIGHT = 64; +type machine = { + number: string, + state: string, + startTime: string, + endTime: string, + donePercent: string, + remainingTime: string, +} + type Props = { - navigation: Object, - route: Object, - theme: Object, - collapsibleStack: Object, + navigation: StackNavigationProp, + theme: CustomTheme, + collapsibleStack: Collapsible, } type State = { refreshing: boolean, modalCurrentDisplayItem: React.Node, - machinesWatched: Array, + machinesWatched: Array, bannerVisible: boolean, }; @@ -47,11 +59,12 @@ class ProxiwashScreen extends React.Component { modalRef: Object; fetchedData: Object; + allMachines: Array; state = { refreshing: false, modalCurrentDisplayItem: null, - machinesWatched: [], + machinesWatched: JSON.parse(AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current), bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === '1', }; @@ -65,6 +78,7 @@ class ProxiwashScreen extends React.Component { modalStateStrings[ProxiwashConstants.machineStates["EN COURS"]] = i18n.t('proxiwashScreen.modal.running'); modalStateStrings[ProxiwashConstants.machineStates.HS] = i18n.t('proxiwashScreen.modal.broken'); modalStateStrings[ProxiwashConstants.machineStates.ERREUR] = i18n.t('proxiwashScreen.modal.error'); + this.allMachines = []; } /** @@ -86,25 +100,6 @@ class ProxiwashScreen extends React.Component { this.props.navigation.setOptions({ headerRight: this.getAboutButton, }); - if (AsyncStorageManager.getInstance().preferences.expoToken.current !== '') { - // Get latest watchlist from server - Notifications.getMachineNotificationWatchlist((fetchedList) => { - this.setState({machinesWatched: fetchedList}) - }); - // Get updated watchlist after received notification - // Expo.Notifications.addListener(() => { - // Notifications.getMachineNotificationWatchlist((fetchedList) => { - // this.setState({machinesWatched: fetchedList}) - // }); - // }); - // if (Platform.OS === 'android') { - // Expo.Notifications.createChannelAndroidAsync('reminders', { - // name: 'Reminders', - // priority: 'max', - // vibrate: [0, 250, 250, 250], - // }); - // } - } } /** @@ -129,8 +124,16 @@ class ProxiwashScreen extends React.Component { * @param item The item to extract the key from * @return {*} The extracted key */ - getKeyExtractor(item: Object) { - return item !== undefined ? item.number : undefined; + getKeyExtractor = (item: machine) => item.number; + + + getMachineEndDate(machine: machine) { + const array = machine.endTime.split(":"); + let date = new Date(); + date.setHours(parseInt(array[0]), parseInt(array[1])); + if (date < new Date()) + date.setDate(date.getDate() + 1); + return date; } /** @@ -138,18 +141,23 @@ class ProxiwashScreen extends React.Component { * 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 machineId The machine's ID + * @param machine The machine to watch * @returns {Promise} */ - setupNotifications(machineId: string) { - if (AsyncStorageManager.getInstance().preferences.expoToken.current !== '') { - if (!this.isMachineWatched(machineId)) { - Notifications.setupMachineNotification(machineId, true); - this.saveNotificationToState(machineId); - } else - this.disableNotification(machineId); + setupNotifications(machine: machine) { + if (!this.isMachineWatched(machine)) { + Notifications.setupMachineNotification(machine.number, true, this.getMachineEndDate(machine)) + .then(() => { + this.saveNotificationToState(machine); + }) + .catch(() => { + this.showNotificationsDisabledWarning(); + }); } else { - this.showNotificationsDisabledWarning(); + Notifications.setupMachineNotification(machine.number, false) + .then(() => { + this.removeNotificationFromState(machine); + }); } } @@ -163,62 +171,79 @@ class ProxiwashScreen extends React.Component { ); } - /** - * Stops scheduled notifications for the machine of the given ID. - * This will also remove the notification if it was already shown. - * - * @param machineId The machine's ID - */ - disableNotification(machineId: string) { - let data = this.state.machinesWatched; - if (data.length > 0) { - let arrayIndex = data.indexOf(machineId); - if (arrayIndex !== -1) { - Notifications.setupMachineNotification(machineId, false); - this.removeNotificationFroState(arrayIndex); - } - } - } - /** * Adds the given notifications associated to a machine ID to the watchlist, and saves the array to the preferences * - * @param machineId + * @param machine */ - saveNotificationToState(machineId: string) { + saveNotificationToState(machine: machine) { let data = this.state.machinesWatched; - data.push(machineId); - this.updateNotificationState(data); + data.push(machine); + this.saveNewWatchedList(data); } /** * Removes the given index from the watchlist array and saves it to preferences * - * @param index + * @param machine */ - removeNotificationFroState(index: number) { + removeNotificationFromState(machine: machine) { let data = this.state.machinesWatched; - data.splice(index, 1); - this.updateNotificationState(data); + 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); } - /** - * Sets the given fetchedData as the watchlist - * - * @param data - */ - updateNotificationState(data: Array) { - this.setState({machinesWatched: data}); + saveNewWatchedList(list: Array) { + this.setState({machinesWatched: list}); + AsyncStorageManager.getInstance().savePref( + AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key, + JSON.stringify(list), + ); } /** * Checks whether the machine of the given ID has scheduled notifications * - * @param machineID The machine's ID + * @param machine * @returns {boolean} */ - isMachineWatched(machineID: string) { - return this.state.machinesWatched.indexOf(machineID) !== -1; + isMachineWatched(machine: machine) { + let watched = false; + const list = this.state.machinesWatched; + for (let i = 0; i < list.length; i++) { + if (list[i].number === machine.number && list[i].endTime === machine.endTime) { + watched = true; + break; + } + } + return watched; + } + + getMachineOfId(id: string) { + for (let i = 0; i < this.allMachines.length; i++) { + if (this.allMachines[i].number === id) + return this.allMachines[i]; + } + return null; + } + + getCleanedMachineWatched() { + const list = this.state.machinesWatched; + let newList = []; + for (let i = 0; i < list.length; i++) { + let machine = this.getMachineOfId(list[i].number); + if (machine !== null + && list[i].number === machine.number && list[i].endTime === machine.endTime + && ProxiwashConstants.machineStates[list[i].state] === ProxiwashConstants.machineStates["EN COURS"]) { + newList.push(machine); + } + } + return newList; } /** @@ -234,8 +259,9 @@ class ProxiwashScreen extends React.Component { AprilFoolsManager.getNewProxiwashDryerOrderedList(data.dryers); AprilFoolsManager.getNewProxiwashWasherOrderedList(data.washers); } - this.fetchedData = fetchedData; - + this.fetchedData = data; + this.allMachines = [...data.dryers, ...data.washers]; + this.state.machinesWatched = this.getCleanedMachineWatched(); return [ { title: i18n.t('proxiwashScreen.dryers'), @@ -271,13 +297,13 @@ class ProxiwashScreen extends React.Component { /** * Callback used when the user clicks on enable notifications for a machine * - * @param machineId The machine's id to set notifications for + * @param machine The machine to set notifications for */ - onSetupNotificationsPress(machineId: string) { + onSetupNotificationsPress(machine: machine) { if (this.modalRef) { this.modalRef.close(); } - this.setupNotifications(machineId) + this.setupNotifications(machine); } /** @@ -296,7 +322,7 @@ class ProxiwashScreen extends React.Component { onPress: undefined }; let message = modalStateStrings[ProxiwashConstants.machineStates[item.state]]; - const onPress = this.onSetupNotificationsPress.bind(this, item.number); + const onPress = this.onSetupNotificationsPress.bind(this, item); if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates["EN COURS"]) { button = { @@ -409,8 +435,8 @@ class ProxiwashScreen extends React.Component { return ( this.onSetupNotificationsPress(item)} + isWatched={this.isMachineWatched(item)} isDryer={isDryer} height={LIST_ITEM_HEIGHT} /> diff --git a/src/utils/Notifications.js b/src/utils/Notifications.js index b83ca38..4bfe7a9 100644 --- a/src/utils/Notifications.js +++ b/src/utils/Notifications.js @@ -1,12 +1,8 @@ // @flow import {checkNotifications, requestNotifications, RESULTS} from 'react-native-permissions'; -// import {Notifications} from 'expo'; -import AsyncStorageManager from "../managers/AsyncStorageManager"; -import LocaleManager from "../managers/LocaleManager"; -import passwords from "../../passwords"; -const EXPO_TOKEN_SERVER = 'https://etud.insa-toulouse.fr/~amicale_app/expo_notifications/save_token.php'; +const PushNotification = require("react-native-push-notification"); /** * Async function asking permission to send notifications to the user @@ -32,50 +28,13 @@ export async function askPermissions() { })); } -/** - * 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} - */ -export async function initExpoToken() { - // let token = AsyncStorageManager.getInstance().preferences.expoToken.current; - // if (token === '') { - // askPermissions().then(() => { - // Notifications.getExpoPushTokenAsync().then((token) => { - // // Save token for instant use later on - // AsyncStorageManager.getInstance().savePref(AsyncStorageManager.getInstance().preferences.expoToken.key, token); - // }); - // }); - // } -} - -/** - * Gets the machines watched from the server - * - * @param callback Function to execute with the fetched data - */ -export function getMachineNotificationWatchlist(callback: Function) { - let token = AsyncStorageManager.getInstance().preferences.expoToken.current; - if (token !== '') { - let data = { - function: 'get_machine_watchlist', - password: passwords.expoNotifications, - token: token, - }; - 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.json()) - .then((responseJson) => { - callback(responseJson); - }); - } +function createNotifications(machineID: string, date: Date) { + PushNotification.localNotificationSchedule({ + title: "Title", + message: "Message", + id: machineID, + date: date, + }); } /** @@ -83,50 +42,22 @@ export function getMachineNotificationWatchlist(callback: Function) { * * @param machineID The machine ID * @param isEnabled True to enable notifications, false to disable + * @param endDate */ -export function setupMachineNotification(machineID: string, isEnabled: boolean) { - let token = AsyncStorageManager.getInstance().preferences.expoToken.current; - if (token !== '') { - let data = { - function: 'setup_machine_notification', - password: passwords.expoNotifications, - locale: LocaleManager.getCurrentLocale(), - 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 - }); - } -} - -/** - * Sends the selected reminder time for notifications to the server - * - * @param time The reminder time to use - */ -export function setMachineReminderNotificationTime(time: number) { - let token = AsyncStorageManager.getInstance().preferences.expoToken.current; - if (token !== '') { - let data = { - function: 'set_machine_reminder', - password: passwords.expoNotifications, - token: token, - time: time, - }; - fetch(EXPO_TOKEN_SERVER, { - method: 'POST', - headers: new Headers({ - Accept: 'application/json', - 'Content-Type': 'application/json', - }), - body: JSON.stringify(data) // <-- Post parameters - }); - } -} +export async function setupMachineNotification(machineID: string, isEnabled: boolean, endDate?: Date) { + return new Promise((resolve, reject) => { + if (isEnabled && endDate != null) { + askPermissions() + .then(() => { + createNotifications(machineID, endDate); + resolve(); + }) + .catch(() => { + reject(); + }); + } else { + PushNotification.cancelLocalNotifications({id: machineID}); + resolve(); + } + }); +} \ No newline at end of file