forked from vergnet/application-amicale
Replaced local notifications with push notifications
This commit is contained in:
parent
728e72b503
commit
8d4223333f
9 changed files with 103 additions and 93 deletions
4
App.js
4
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<Props, State> {
|
|||
});
|
||||
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,
|
||||
|
|
|
@ -17,11 +17,9 @@ type State = {
|
|||
refreshing: boolean,
|
||||
firstLoading: boolean,
|
||||
fetchedData: Object,
|
||||
machinesWatched: Array<Object>,
|
||||
machinesWatched: Array<string>,
|
||||
};
|
||||
|
||||
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<Props, State
|
|||
refreshTime: number;
|
||||
lastRefresh: Date;
|
||||
|
||||
minTimeBetweenRefresh = 60;
|
||||
|
||||
constructor(fetchUrl: string, refreshTime: number) {
|
||||
super();
|
||||
this.webDataManager = new WebDataManager(fetchUrl);
|
||||
|
@ -65,6 +65,10 @@ export default class FetchedDataSectionList extends React.Component<Props, State
|
|||
return ["whoa", "nah"];
|
||||
}
|
||||
|
||||
setMinTimeRefresh(value: number) {
|
||||
this.minTimeBetweenRefresh = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register react navigation events on first screen load.
|
||||
* Allows to detect when the screen is focused
|
||||
|
@ -117,7 +121,7 @@ export default class FetchedDataSectionList extends React.Component<Props, State
|
|||
_onRefresh = () => {
|
||||
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;
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<Props> {
|
|||
super();
|
||||
this.customInjectedJS =
|
||||
'document.querySelector(\'head\').innerHTML += \'<meta name="viewport" content="width=device-width, initial-scale=1.0">\';' +
|
||||
'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/planex/customMobile.css" type="text/css"/>\';';
|
||||
'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="' + CUSTOM_CSS_GENERAL + '" type="text/css"/>\';';
|
||||
if (ThemeManager.getNightMode())
|
||||
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"/>\';';
|
||||
this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="' + CUSTOM_CSS_NIGHTMODE + '" type="text/css"/>\';';
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<Props> {
|
||||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
return (
|
||||
|
|
|
@ -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<void>}
|
||||
*/
|
||||
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<string | null> {
|
||||
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<Object>) {
|
||||
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")
|
||||
|
|
|
@ -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<Props> {
|
|||
super();
|
||||
this.customInjectedJS =
|
||||
'document.querySelector(\'head\').innerHTML += \'<meta name="viewport" content="width=device-width, initial-scale=1.0">\';' +
|
||||
'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="https://srv-falcon.etud.insa-toulouse.fr/~vergnet/appli-amicale/RU/customGeneral.css" type="text/css"/>\';';
|
||||
'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="' + CUSTOM_CSS_GENERAL + '" type="text/css"/>\';';
|
||||
if (!ThemeManager.getNightMode())
|
||||
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"/>\';';
|
||||
this.customInjectedJS += 'document.querySelector(\'head\').innerHTML += \'<link rel="stylesheet" href="' + CUSTOM_CSS_LIGHT + '" type="text/css"/>\';';
|
||||
}
|
||||
|
||||
getRefreshButton() {
|
||||
|
|
|
@ -43,6 +43,11 @@ export default class AsyncStorageManager {
|
|||
key: 'nightMode',
|
||||
default: '0',
|
||||
current: '',
|
||||
},
|
||||
expoToken: {
|
||||
key: 'expoToken',
|
||||
default: '',
|
||||
current: '',
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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<import("react").ReactText>} Notification Id
|
||||
*/
|
||||
static async scheduleNotification(title: string, body: string, time: number, data: Object, androidChannelID: string): Promise<string> {
|
||||
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<void>}
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue