Added local notifications on android

This commit is contained in:
Arnaud Vergnet 2020-04-30 14:04:31 +02:00
parent f8ded811f4
commit 087331258a
9 changed files with 171 additions and 185 deletions

1
.gitignore vendored
View file

@ -10,7 +10,6 @@ web-build/
web-report/
/.expo-shared/
/package-lock.json
/passwords.json
!/.idea/
/.idea/*

2
App.js
View file

@ -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<Props, State> {
*/
loadAssetsAsync = async () => {
await this.storageManager.loadPreferences();
await initExpoToken();
try {
await ConnectionManager.getInstance().recoverLogin();
} catch (e) {

View file

@ -1,7 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.amicaleinsat.application">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
@ -14,6 +16,33 @@
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
>
<!-- NOTIFICATIONS -->
<meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_name"
android:value="reminders"/>
<meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_description"
android:value="reminders"/>
<!-- Change the resource name to your App's accent color - or any other color you want -->
<meta-data android:name="com.dieam.reactnativepushnotification.notification_color"
android:resource="@color/colorPrimary"/> <!-- or @android:color/{name} to use a standard color -->
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
android:exported="false" >
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- END NOTIFICATIONS-->
<meta-data android:name="com.facebook.sdk.AutoInitEnabled" android:value="false"/>
<meta-data android:name="com.facebook.sdk.AutoLogAppEventsEnabled" android:value="false"/>
<meta-data android:name="com.facebook.sdk.AdvertiserIDCollectionEnabled" android:value="false"/>

View file

@ -3,7 +3,9 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>
<uses-permission tools:node="remove" android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
@ -21,5 +23,5 @@
<uses-permission tools:node="remove" android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission tools:node="remove" android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission tools:node="remove" android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission tools:node="remove" android:name="android.permission.WAKE_LOCK"/>
</manifest>

View file

@ -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",

View file

@ -69,6 +69,11 @@ export default class AsyncStorageManager {
default: '1',
current: '',
},
proxiwashWatchedMachines: {
key: 'proxiwashWatchedMachines',
default: '[]',
current: '',
},
planexShowBanner: {
key: 'planexShowBanner',
default: '1',

View file

@ -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<Props, State> {
this.setState({
proxiwashNotifPickerSelected: value
});
let intVal = 0;
if (value !== 'never')
intVal = parseInt(value);
setMachineReminderNotificationTime(intVal);
}
};

View file

@ -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<string>,
machinesWatched: Array<machine>,
bannerVisible: boolean,
};
@ -47,11 +59,12 @@ class ProxiwashScreen extends React.Component<Props, State> {
modalRef: Object;
fetchedData: Object;
allMachines: Array<machine>;
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<Props, State> {
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<Props, State> {
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<Props, State> {
* @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<Props, State> {
* 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<void>}
*/
setupNotifications(machineId: string) {
if (AsyncStorageManager.getInstance().preferences.expoToken.current !== '') {
if (!this.isMachineWatched(machineId)) {
Notifications.setupMachineNotification(machineId, true);
this.saveNotificationToState(machineId);
} else
this.disableNotification(machineId);
} else {
setupNotifications(machine: machine) {
if (!this.isMachineWatched(machine)) {
Notifications.setupMachineNotification(machine.number, true, this.getMachineEndDate(machine))
.then(() => {
this.saveNotificationToState(machine);
})
.catch(() => {
this.showNotificationsDisabledWarning();
});
} else {
Notifications.setupMachineNotification(machine.number, false)
.then(() => {
this.removeNotificationFromState(machine);
});
}
}
@ -163,62 +171,79 @@ class ProxiwashScreen extends React.Component<Props, State> {
);
}
/**
* 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<Object>) {
this.setState({machinesWatched: data});
saveNewWatchedList(list: Array<machine>) {
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<Props, State> {
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<Props, State> {
/**
* 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<Props, State> {
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<Props, State> {
return (
<ProxiwashListItem
item={item}
onPress={this.showModal}
isWatched={this.isMachineWatched(item.number)}
onPress={() => this.onSetupNotificationsPress(item)}
isWatched={this.isMachineWatched(item)}
isDryer={isDryer}
height={LIST_ITEM_HEIGHT}
/>

View file

@ -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,101 +28,36 @@ 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<void>}
*/
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,
});
}
}
/**
* Asks the server to enable/disable notifications for the specified machine
*
* @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
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();
}
});
}
}
/**
* 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
});
}
}