Update proxiwash screen and notifications
This commit is contained in:
parent
e08fdc7c37
commit
27199b85e5
8 changed files with 252 additions and 329 deletions
|
@ -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"/>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
15
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() });
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue