Update proxiwash screen and notifications

This commit is contained in:
Arnaud Vergnet 2021-05-12 23:01:41 +02:00
parent e08fdc7c37
commit 27199b85e5
8 changed files with 252 additions and 329 deletions

View file

@ -8,7 +8,6 @@
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<application
android:name=".MainApplication"
@ -19,31 +18,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"/>
<!-- START NOTIFICATIONS -->
<!-- Change the value to true to enable pop-up for in foreground on receiving remote notifications (for prevent duplicating while showing local notifications set this to false) -->
<meta-data android:name="com.dieam.reactnativepushnotification.notification_foreground"
android:value="false"/>
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"/>
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
<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"/>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>
<service
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
android:exported="false">
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
android:exported="false" >
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- END NOTIFICATIONS-->
<!-- END NOTIFICATIONS -->
<meta-data android:name="com.facebook.sdk.AutoInitEnabled" android:value="false"/>

View file

@ -5,22 +5,11 @@ import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import android.content.Intent;
import android.content.res.Configuration;
import org.devio.rn.splashscreen.SplashScreen;
public class MainActivity extends ReactActivity {
// Added automatically by Expo Config
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
sendBroadcast(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this, R.style.SplashScreenTheme);

View file

@ -21,7 +21,7 @@
<uses-permission tools:node="remove" android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission tools:node="remove" android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission tools:node="remove" android:name="android.permission.RECORD_AUDIO"/>
<uses-permission tools:node="remove" android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission tools:node="remove" android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission tools:node="remove" android:name="android.permission.ACCESS_NETWORK_STATE"/>
</manifest>

15
package-lock.json generated
View file

@ -2530,6 +2530,12 @@
"@types/xdate": "*"
}
},
"@types/react-native-push-notification": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@types/react-native-push-notification/-/react-native-push-notification-7.2.0.tgz",
"integrity": "sha512-4kErWFa0qit8qzPB6Nbp7kG9NiwDyKu5XxrNlrCIc1zoFxu48ABeofVvNCKv2RtlmFvCftibtykeysRZCeuT8A==",
"dev": true
},
"@types/react-native-vector-icons": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.6.tgz",
@ -10684,12 +10690,9 @@
"integrity": "sha512-8xiEnU29qHZcT05XXwhPHiLChTt82Pn5Z/nFdDOYGNFZ+IYSbYeGmIxFpratCRO6dgLptNaDFDPiyw2X7UZTeg=="
},
"react-native-push-notification": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/react-native-push-notification/-/react-native-push-notification-5.1.1.tgz",
"integrity": "sha512-CJmKqzM2P/s+a9PImoaiUN4TP1+K4YfmG1B0uUbavgFdGhTtRPTLLwDfFk2h3J6VmTXNak82rUz2iGwyptHm5w==",
"requires": {
"@react-native-community/push-notification-ios": "^1.4.0"
}
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/react-native-push-notification/-/react-native-push-notification-7.3.0.tgz",
"integrity": "sha512-Ofy8dYAhIkFJKxQDvAn7BnXxwtun1SMnqLjZUhRTRzhPEqN0tW7TmIjfyYanNf/lnggRYZqFO+b14Ul3nhlMGw=="
},
"react-native-reanimated": {
"version": "1.13.2",

View file

@ -45,7 +45,7 @@
"react-native-modalize": "2.0.8",
"react-native-paper": "4.8.1",
"react-native-permissions": "3.0.3",
"react-native-push-notification": "5.1.1",
"react-native-push-notification": "7.3.0",
"react-native-reanimated": "1.13.2",
"react-native-render-html": "5.1.0",
"react-native-safe-area-context": "3.2.0",
@ -65,6 +65,7 @@
"@types/react": "17.0.3",
"@types/react-native": "0.64.4",
"@types/react-native-calendars": "1.20.10",
"@types/react-native-push-notification": "^7.2.0",
"@types/react-native-vector-icons": "6.4.6",
"@types/react-test-renderer": "17.0.1",
"@typescript-eslint/eslint-plugin": "4.22.1",
@ -94,7 +95,9 @@
"rules": {
"no-undef": 0,
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/no-shadow": [
"error"
],
"prettier/prettier": [
"error",
{

View file

@ -17,27 +17,28 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
import * as React from 'react';
import React, { Ref } from 'react';
import { useTheme } from 'react-native-paper';
import { Modalize } from 'react-native-modalize';
import { View } from 'react-native-animatable';
import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
type Props = {
children?: React.ReactChild | null;
};
/**
* Abstraction layer for Modalize component, using custom configuration
*
* @param props Props to pass to the element. Must specify an onRef prop to get an Modalize ref.
* @return {*}
*/
function CustomModal(props: {
onRef: (re: Modalize) => void;
children?: React.ReactNode;
}) {
function CustomModal(props: Props, ref?: Ref<Modalize>) {
const theme = useTheme();
const { onRef, children } = props;
const { children } = props;
return (
<Modalize
ref={onRef}
ref={ref}
adjustToContentHeight
handlePosition="inside"
modalStyle={{ backgroundColor: theme.colors.card }}
@ -54,4 +55,4 @@ function CustomModal(props: {
);
}
export default CustomModal;
export default React.forwardRef(CustomModal);

View file

@ -17,20 +17,17 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
import * as React from 'react';
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
import {
Alert,
SectionListData,
SectionListRenderItemInfo,
StyleSheet,
View,
} from 'react-native';
import i18n from 'i18n-js';
import { Avatar, Button, Card, Text, withTheme } from 'react-native-paper';
import { StackNavigationProp } from '@react-navigation/stack';
import { Avatar, Button, Card, Text, useTheme } from 'react-native-paper';
import { Modalize } from 'react-native-modalize';
import WebSectionList from '../../components/Screens/WebSectionList';
import * as Notifications from '../../utils/Notifications';
import AsyncStorageManager from '../../managers/AsyncStorageManager';
import ProxiwashListItem from '../../components/Lists/Proxiwash/ProxiwashListItem';
import ProxiwashConstants, {
@ -53,6 +50,8 @@ import type { SectionListDataType } from '../../components/Screens/WebSectionLis
import type { LaundromatType } from './ProxiwashAboutScreen';
import GENERAL_STYLES from '../../constants/Styles';
import { readData } from '../../utils/WebData';
import { useFocusEffect, useNavigation } from '@react-navigation/core';
import { setupMachineNotification } from '../../utils/Notifications';
const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds
const LIST_ITEM_HEIGHT = 64;
@ -68,17 +67,6 @@ export type ProxiwashMachineType = {
program: string;
};
type PropsType = {
navigation: StackNavigationProp<any>;
theme: ReactNativePaper.Theme;
};
type StateType = {
modalCurrentDisplayItem: React.ReactNode;
machinesWatched: Array<ProxiwashMachineType>;
selectedWash: string;
};
type FetchedDataType = {
dryers: Array<ProxiwashMachineType>;
washers: Array<ProxiwashMachineType>;
@ -99,22 +87,28 @@ const styles = StyleSheet.create({
},
});
/**
* Class defining the app's proxiwash screen. This screen shows information about washing machines and
* dryers, taken from a scrapper reading proxiwash website
*/
class ProxiwashScreen extends React.Component<PropsType, StateType> {
/**
* Shows a warning telling the user notifications are disabled for the app
*/
static showNotificationsDisabledWarning() {
Alert.alert(
i18n.t('screens.proxiwash.modal.notificationErrorTitle'),
i18n.t('screens.proxiwash.modal.notificationErrorDescription')
);
}
function ProxiwashScreen() {
const navigation = useNavigation();
const theme = useTheme();
const [
modalCurrentDisplayItem,
setModalCurrentDisplayItem,
] = useState<React.ReactElement | null>(null);
const [machinesWatched, setMachinesWatched] = useState<
Array<ProxiwashMachineType>
>(
AsyncStorageManager.getObject(
AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key
)
);
modalStateStrings: { [key in MachineStates]: string } = {
const [selectedWash, setSelectedWash] = useState(
AsyncStorageManager.getString(
AsyncStorageManager.PREFERENCES.selectedWash.key
)
);
const modalStateStrings: { [key in MachineStates]: string } = {
[MachineStates.AVAILABLE]: i18n.t('screens.proxiwash.modal.ready'),
[MachineStates.RUNNING]: i18n.t('screens.proxiwash.modal.running'),
[MachineStates.RUNNING_NOT_STARTED]: i18n.t(
@ -126,95 +120,48 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
[MachineStates.UNKNOWN]: i18n.t('screens.proxiwash.modal.unknown'),
};
modalRef: null | Modalize;
const modalRef = useRef<Modalize>(null);
fetchedData: {
dryers: Array<ProxiwashMachineType>;
washers: Array<ProxiwashMachineType>;
};
/**
* Creates machine state parameters using current theme and translations
*/
constructor(props: PropsType) {
super(props);
this.modalRef = null;
this.fetchedData = { dryers: [], washers: [] };
this.state = {
modalCurrentDisplayItem: null,
machinesWatched: AsyncStorageManager.getObject(
AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key
),
selectedWash: AsyncStorageManager.getString(
AsyncStorageManager.PREFERENCES.selectedWash.key
),
};
}
/**
* Setup notification channel for android and add listeners to detect notifications fired
*/
componentDidMount() {
const { navigation } = this.props;
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<MaterialHeaderButtons>
<Item
title="switch"
iconName="swap-horizontal"
onPress={(): void => navigation.navigate('settings')}
title={'switch'}
iconName={'swap-horizontal'}
onPress={() => navigation.navigate('settings')}
/>
<Item
title="information"
iconName="information"
onPress={this.onAboutPress}
title={'information'}
iconName={'information'}
onPress={() => navigation.navigate('proxiwash-about')}
/>
</MaterialHeaderButtons>
),
});
navigation.addListener('focus', this.onScreenFocus);
}
}, [navigation]);
onScreenFocus = () => {
const { state } = this;
const selected = AsyncStorageManager.getString(
AsyncStorageManager.PREFERENCES.selectedWash.key
);
if (selected !== state.selectedWash) {
this.setState({
selectedWash: selected,
});
}
};
/**
* Callback used when pressing the about button.
* This will open the ProxiwashAboutScreen.
*/
onAboutPress = () => {
const { navigation } = this.props;
navigation.navigate('proxiwash-about');
};
useFocusEffect(
useCallback(() => {
const selected = AsyncStorageManager.getString(
AsyncStorageManager.PREFERENCES.selectedWash.key
);
if (selected !== selectedWash) {
setSelectedWash(selected);
}
}, [selectedWash])
);
/**
* Callback used when the user clicks on enable notifications for a machine
*
* @param machine The machine to set notifications for
*/
onSetupNotificationsPress(machine: ProxiwashMachineType) {
if (this.modalRef) {
this.modalRef.close();
const onSetupNotificationsPress = (machine: ProxiwashMachineType) => {
if (modalRef.current) {
modalRef.current.close();
}
this.setupNotifications(machine);
}
/**
* Callback used when receiving modal ref
*
* @param ref
*/
onModalRef = (ref: Modalize) => {
this.modalRef = ref;
setupNotifications(machine);
};
/**
@ -226,11 +173,14 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
* @param isDryer True if the given item is a dryer
* @return {*}
*/
getModalContent(title: string, item: ProxiwashMachineType, isDryer: boolean) {
const { props, state } = this;
const getModalContent = (
title: string,
item: ProxiwashMachineType,
isDryer: boolean
) => {
let button: { text: string; icon: string; onPress: () => void } | undefined;
let message = this.modalStateStrings[item.state];
const onPress = () => this.onSetupNotificationsPress(item);
let message = modalStateStrings[item.state];
const onPress = () => onSetupNotificationsPress(item);
if (item.state === MachineStates.RUNNING) {
let remainingTime = parseInt(item.remainingTime, 10);
if (remainingTime < 0) {
@ -238,7 +188,7 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
}
button = {
text: isMachineWatched(item, state.machinesWatched)
text: isMachineWatched(item, machinesWatched)
? i18n.t('screens.proxiwash.modal.disableNotifications')
: i18n.t('screens.proxiwash.modal.enableNotifications'),
icon: '',
@ -258,7 +208,7 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
left={() => (
<Avatar.Icon
icon={isDryer ? 'tumble-dryer' : 'washing-machine'}
color={props.theme.colors.text}
color={theme.colors.text}
style={styles.icon}
/>
)}
@ -281,7 +231,7 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
) : null}
</View>
);
}
};
/**
* Gets the section render item
@ -289,13 +239,13 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
* @param section The section to render
* @return {*}
*/
getRenderSectionHeader = ({
const getRenderSectionHeader = ({
section,
}: {
section: SectionListData<ProxiwashMachineType>;
}) => {
const isDryer = section.title === i18n.t('screens.proxiwash.dryers');
const nbAvailable = this.getMachineAvailableNumber(isDryer);
const nbAvailable = getMachineAvailableNumber(section.data);
return (
<ProxiwashSectionHeader
title={section.title}
@ -312,13 +262,14 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
* @param section The object describing the current SectionList section
* @returns {React.Node}
*/
getRenderItem = (data: SectionListRenderItemInfo<ProxiwashMachineType>) => {
const { machinesWatched } = this.state;
const getRenderItem = (
data: SectionListRenderItemInfo<ProxiwashMachineType>
) => {
const isDryer = data.section.title === i18n.t('screens.proxiwash.dryers');
return (
<ProxiwashListItem
item={data.item}
onPress={this.showModal}
onPress={showModal}
isWatched={isMachineWatched(data.item, machinesWatched)}
isDryer={isDryer}
height={LIST_ITEM_HEIGHT}
@ -332,7 +283,7 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
* @param item The item to extract the key from
* @return {*} The extracted key
*/
getKeyExtractor = (item: ProxiwashMachineType): string => item.number;
const getKeyExtractor = (item: ProxiwashMachineType): string => item.number;
/**
* Setups notifications for the machine with the given ID.
@ -341,28 +292,19 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
*
* @param machine The machine to watch
*/
setupNotifications(machine: ProxiwashMachineType) {
const { machinesWatched } = this.state;
const setupNotifications = (machine: ProxiwashMachineType) => {
if (!isMachineWatched(machine, machinesWatched)) {
Notifications.setupMachineNotification(
setupMachineNotification(
machine.number,
true,
getMachineEndDate(machine)
)
.then(() => {
this.saveNotificationToState(machine);
})
.catch(() => {
ProxiwashScreen.showNotificationsDisabledWarning();
});
} else {
Notifications.setupMachineNotification(machine.number, false, null).then(
() => {
this.removeNotificationFromState(machine);
}
);
saveNotificationToState(machine);
} else {
setupMachineNotification(machine.number, false);
removeNotificationFromState(machine);
}
}
};
/**
* Gets the number of machines available
@ -370,13 +312,9 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
* @param isDryer True if we are only checking for dryer, false for washers
* @return {number} The number of machines available
*/
getMachineAvailableNumber(isDryer: boolean): number {
let data;
if (isDryer) {
data = this.fetchedData.dryers;
} else {
data = this.fetchedData.washers;
}
const getMachineAvailableNumber = (
data: ReadonlyArray<ProxiwashMachineType>
): number => {
let count = 0;
data.forEach((machine: ProxiwashMachineType) => {
if (machine.state === MachineStates.AVAILABLE) {
@ -384,7 +322,7 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
}
});
return count;
}
};
/**
* Creates the dataset to be used by the FlatList
@ -392,10 +330,9 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
* @param fetchedData
* @return {*}
*/
createDataset = (
const createDataset = (
fetchedData: FetchedDataType | undefined
): SectionListDataType<ProxiwashMachineType> => {
const { state } = this;
if (fetchedData) {
let data = fetchedData;
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
@ -403,24 +340,26 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
AprilFoolsManager.getNewProxiwashDryerOrderedList(data.dryers);
AprilFoolsManager.getNewProxiwashWasherOrderedList(data.washers);
}
this.fetchedData = data;
// TODO dirty, should be refactored
this.state.machinesWatched = getCleanedMachineWatched(
state.machinesWatched,
[...data.dryers, ...data.washers]
);
fetchedData = data;
const cleanedList = getCleanedMachineWatched(machinesWatched, [
...data.dryers,
...data.washers,
]);
if (cleanedList !== machinesWatched) {
setMachinesWatched(machinesWatched);
}
return [
{
title: i18n.t('screens.proxiwash.dryers'),
icon: 'tumble-dryer',
data: data.dryers === undefined ? [] : data.dryers,
keyExtractor: this.getKeyExtractor,
keyExtractor: getKeyExtractor,
},
{
title: i18n.t('screens.proxiwash.washers'),
icon: 'washing-machine',
data: data.washers === undefined ? [] : data.washers,
keyExtractor: this.getKeyExtractor,
keyExtractor: getKeyExtractor,
},
];
} else {
@ -428,15 +367,6 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
}
};
/**
* Callback used when the user clicks on the navigate to settings button.
* This will hide the banner and open the SettingsScreen
*/
onGoToSettings = () => {
const { navigation } = this.props;
navigation.navigate('settings');
};
/**
* Shows a modal for the given item
*
@ -444,12 +374,14 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
* @param item The item to display information for in the modal
* @param isDryer True if the given item is a dryer
*/
showModal = (title: string, item: ProxiwashMachineType, isDryer: boolean) => {
this.setState({
modalCurrentDisplayItem: this.getModalContent(title, item, isDryer),
});
if (this.modalRef) {
this.modalRef.open();
const showModal = (
title: string,
item: ProxiwashMachineType,
isDryer: boolean
) => {
setModalCurrentDisplayItem(getModalContent(title, item, isDryer));
if (modalRef.current) {
modalRef.current.open();
}
};
@ -458,87 +390,76 @@ class ProxiwashScreen extends React.Component<PropsType, StateType> {
*
* @param machine
*/
saveNotificationToState(machine: ProxiwashMachineType) {
const { machinesWatched } = this.state;
const data = machinesWatched;
const saveNotificationToState = (machine: ProxiwashMachineType) => {
let data = [...machinesWatched];
data.push(machine);
this.saveNewWatchedList(data);
}
saveNewWatchedList(data);
};
/**
* Removes the given index from the watchlist array and saves it to preferences
*
* @param selectedMachine
*/
removeNotificationFromState(selectedMachine: ProxiwashMachineType) {
const { machinesWatched } = this.state;
const newList = [...machinesWatched];
machinesWatched.forEach((machine: ProxiwashMachineType, index: number) => {
if (
machine.number === selectedMachine.number &&
machine.endTime === selectedMachine.endTime
) {
newList.splice(index, 1);
}
});
this.saveNewWatchedList(newList);
}
const removeNotificationFromState = (
selectedMachine: ProxiwashMachineType
) => {
const newList = machinesWatched.filter(
(m) => m.number !== selectedMachine.number
);
saveNewWatchedList(newList);
};
saveNewWatchedList(list: Array<ProxiwashMachineType>) {
this.setState({ machinesWatched: list });
const saveNewWatchedList = (list: Array<ProxiwashMachineType>) => {
setMachinesWatched(list);
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key,
list
);
}
};
render() {
const { state } = this;
let data: LaundromatType;
switch (state.selectedWash) {
case 'tripodeB':
data = ProxiwashConstants.tripodeB;
break;
default:
data = ProxiwashConstants.washinsa;
}
return (
<View style={GENERAL_STYLES.flex}>
<View style={styles.container}>
<WebSectionList
request={() => readData<FetchedDataType>(data.url)}
createDataset={this.createDataset}
renderItem={this.getRenderItem}
renderSectionHeader={this.getRenderSectionHeader}
autoRefreshTime={REFRESH_TIME}
refreshOnFocus={true}
extraData={state.machinesWatched.length}
/>
</View>
<MascotPopup
prefKey={AsyncStorageManager.PREFERENCES.proxiwashShowMascot.key}
title={i18n.t('screens.proxiwash.mascotDialog.title')}
message={i18n.t('screens.proxiwash.mascotDialog.message')}
icon="information"
buttons={{
action: {
message: i18n.t('screens.proxiwash.mascotDialog.ok'),
icon: 'cog',
onPress: this.onGoToSettings,
},
cancel: {
message: i18n.t('screens.proxiwash.mascotDialog.cancel'),
icon: 'close',
},
}}
emotion={MASCOT_STYLE.NORMAL}
/>
<CustomModal onRef={this.onModalRef}>
{state.modalCurrentDisplayItem}
</CustomModal>
</View>
);
let data: LaundromatType;
switch (selectedWash) {
case 'tripodeB':
data = ProxiwashConstants.tripodeB;
break;
default:
data = ProxiwashConstants.washinsa;
}
return (
<View style={GENERAL_STYLES.flex}>
<View style={styles.container}>
<WebSectionList
request={() => readData<FetchedDataType>(data.url)}
createDataset={createDataset}
renderItem={getRenderItem}
renderSectionHeader={getRenderSectionHeader}
autoRefreshTime={REFRESH_TIME}
refreshOnFocus={true}
extraData={machinesWatched.length}
/>
</View>
<MascotPopup
prefKey={AsyncStorageManager.PREFERENCES.proxiwashShowMascot.key}
title={i18n.t('screens.proxiwash.mascotDialog.title')}
message={i18n.t('screens.proxiwash.mascotDialog.message')}
icon="information"
buttons={{
action: {
message: i18n.t('screens.proxiwash.mascotDialog.ok'),
icon: 'cog',
onPress: () => navigation.navigate('settings'),
},
cancel: {
message: i18n.t('screens.proxiwash.mascotDialog.cancel'),
icon: 'close',
},
}}
emotion={MASCOT_STYLE.NORMAL}
/>
<CustomModal ref={modalRef}>{modalCurrentDisplayItem}</CustomModal>
</View>
);
}
export default withTheme(ProxiwashScreen);
export default ProxiwashScreen;

View file

@ -17,44 +17,59 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
import {
checkNotifications,
requestNotifications,
RESULTS,
} from 'react-native-permissions';
import i18n from 'i18n-js';
import AsyncStorageManager from '../managers/AsyncStorageManager';
const PushNotification = require('react-native-push-notification');
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import PushNotification from 'react-native-push-notification';
import { Platform } from 'react-native';
// Used to multiply the normal notification id to create the reminder one. It allows to find it back easily
const reminderIdFactor = 100;
/**
* Async function asking permission to send notifications to the user.
* Used on ios.
*
* @returns {Promise<void>}
*/
export async function askPermissions(): Promise<void> {
return new Promise((resolve: () => void, reject: () => void) => {
checkNotifications().then(({ status }: { status: string }) => {
if (status === RESULTS.GRANTED) {
resolve();
} else if (status === RESULTS.BLOCKED) {
reject();
} else {
requestNotifications([]).then((result: { status: string }) => {
if (result.status === RESULTS.GRANTED) {
resolve();
} else {
reject();
}
});
}
});
});
}
PushNotification.createChannel(
{
channelId: 'reminders', // (required)
channelName: 'Reminders', // (required)
channelDescription: 'Get laundry reminders', // (optional) default: undefined.
playSound: true, // (optional) default: true
soundName: 'default', // (optional) See `soundName` parameter of `localNotification` function
importance: 4, // (optional) default: 4. Int value of the Android notification importance
vibrate: true, // (optional) default: true. Creates the default vibration patten if true.
},
(created) => console.log(`createChannel returned '${created}'`) // (optional) callback returns whether the channel was created, false means it already existed.
);
PushNotification.configure({
// (required) Called when a remote is received or opened, or local notification is opened
onNotification: function (notification) {
console.log('NOTIFICATION:', notification);
// process the notification
// (required) Called when a remote is received or opened, or local notification is opened
notification.finish(PushNotificationIOS.FetchResult.NoData);
},
// IOS ONLY (optional): default: all - Permissions to register.
permissions: {
alert: true,
badge: true,
sound: true,
},
// Should the initial notification be popped automatically
// default: true
popInitialNotification: true,
/**
* (optional) default: true
* - Specified if permissions (ios) and token (android and ios) will requested or not,
* - if not, you must call PushNotificationsHandler.requestPermissions() later
* - if you are not using remote notification or do not have Firebase installed, use this:
* requestPermissions: Platform.OS === 'ios'
*/
requestPermissions: Platform.OS === 'ios',
});
/**
* Creates a notification for the given machine id at the given date.
@ -79,7 +94,7 @@ function createNotifications(machineID: string, date: Date) {
message: i18n.t('screens.proxiwash.notifications.machineRunningBody', {
number: machineID,
}),
id: id.toString(),
id: id,
date: reminderDate,
});
}
@ -89,7 +104,7 @@ function createNotifications(machineID: string, date: Date) {
message: i18n.t('screens.proxiwash.notifications.machineFinishedBody', {
number: machineID,
}),
id: machineID,
id: parseInt(machineID, 10),
date,
});
}
@ -104,26 +119,16 @@ function createNotifications(machineID: string, date: Date) {
* @param isEnabled True to enable notifications, false to disable
* @param endDate The trigger date, or null if disabling notifications
*/
export async function setupMachineNotification(
export function setupMachineNotification(
machineID: string,
isEnabled: boolean,
endDate: Date | null
): Promise<void> {
return new Promise((resolve: () => void, reject: () => void) => {
if (isEnabled && endDate != null) {
askPermissions()
.then(() => {
createNotifications(machineID, endDate);
resolve();
})
.catch(() => {
reject();
});
} else {
PushNotification.cancelLocalNotifications({ id: machineID });
const reminderId = reminderIdFactor * parseInt(machineID, 10);
PushNotification.cancelLocalNotifications({ id: reminderId.toString() });
resolve();
}
});
endDate?: Date | null
) {
if (isEnabled && endDate) {
createNotifications(machineID, endDate);
} else {
PushNotification.cancelLocalNotifications({ id: machineID });
const reminderId = reminderIdFactor * parseInt(machineID, 10);
PushNotification.cancelLocalNotifications({ id: reminderId.toString() });
}
}