Compare commits

..

No commits in common. "ddfac76f4e150a194df2c7863e09f6afdae1a456" and "7fb4de3c5bc4acb5e0f07f9523a6aafbac8a1237" have entirely different histories.

23 changed files with 170 additions and 414 deletions

View file

@ -1,142 +0,0 @@
import React from 'react';
import {getCleanedMachineWatched, getMachineEndDate, getMachineOfId, isMachineWatched} from "../../src/utils/Proxiwash";
test('getMachineEndDate', () => {
jest.spyOn(Date, 'now')
.mockImplementation(() =>
new Date('2020-01-14T15:00:00.000Z').getTime()
);
let expectDate = new Date('2020-01-14T15:00:00.000Z');
expectDate.setHours(23);
expectDate.setMinutes(10);
expect(getMachineEndDate({endTime: "23:10"}).getTime()).toBe(expectDate.getTime());
expectDate.setHours(16);
expectDate.setMinutes(30);
expect(getMachineEndDate({endTime: "16:30"}).getTime()).toBe(expectDate.getTime());
expect(getMachineEndDate({endTime: "15:30"})).toBeNull();
expect(getMachineEndDate({endTime: "13:10"})).toBeNull();
jest.spyOn(Date, 'now')
.mockImplementation(() =>
new Date('2020-01-14T23:00:00.000Z').getTime()
);
expectDate = new Date('2020-01-14T23:00:00.000Z');
expectDate.setHours(0);
expectDate.setMinutes(30);
expect(getMachineEndDate({endTime: "00:30"}).getTime()).toBe(expectDate.getTime());
});
test('isMachineWatched', () => {
let machineList = [
{
number: "0",
endTime: "23:30",
},
{
number: "1",
endTime: "20:30",
},
];
expect(isMachineWatched({number: "0", endTime: "23:30"}, machineList)).toBeTrue();
expect(isMachineWatched({number: "1", endTime: "20:30"}, machineList)).toBeTrue();
expect(isMachineWatched({number: "3", endTime: "20:30"}, machineList)).toBeFalse();
expect(isMachineWatched({number: "1", endTime: "23:30"}, machineList)).toBeFalse();
});
test('getMachineOfId', () => {
let machineList = [
{
number: "0",
},
{
number: "1",
},
];
expect(getMachineOfId("0", machineList)).toStrictEqual({number: "0"});
expect(getMachineOfId("1", machineList)).toStrictEqual({number: "1"});
expect(getMachineOfId("3", machineList)).toBeNull();
});
test('getCleanedMachineWatched', () => {
let machineList = [
{
number: "0",
endTime: "23:30",
},
{
number: "1",
endTime: "20:30",
},
{
number: "2",
endTime: "",
},
];
let watchList = [
{
number: "0",
endTime: "23:30",
},
{
number: "1",
endTime: "20:30",
},
{
number: "2",
endTime: "",
},
];
let cleanedList = watchList;
expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual(cleanedList);
watchList = [
{
number: "0",
endTime: "23:30",
},
{
number: "1",
endTime: "20:30",
},
{
number: "2",
endTime: "15:30",
},
];
cleanedList = [
{
number: "0",
endTime: "23:30",
},
{
number: "1",
endTime: "20:30",
},
];
expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual(cleanedList);
watchList = [
{
number: "0",
endTime: "23:30",
},
{
number: "1",
endTime: "20:31",
},
{
number: "3",
endTime: "15:30",
},
];
cleanedList = [
{
number: "0",
endTime: "23:30",
},
];
expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual(cleanedList);
});

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources> <resources>
<color name="activityBackground">#be1522</color> <color name="activityBackground">#ffffff</color>
<color name="navigationBarColor">#121212</color> <color name="navigationBarColor">#121212</color>
<color name="colorPrimaryDark">#be1522</color> <color name="colorPrimaryDark">#be1522</color>
<color name="colorPrimary">#be1522</color> <color name="colorPrimary">#be1522</color>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/ios.icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View file

@ -20,13 +20,12 @@
}, },
"dependencies": { "dependencies": {
"@nartc/react-native-barcode-mask": "^1.1.9", "@nartc/react-native-barcode-mask": "^1.1.9",
"@react-native-community/async-storage": "^1.9.0", "@react-native-community/masked-view": "0.1.6",
"@react-native-community/masked-view": "^0.1.10",
"@react-native-community/push-notification-ios": "^1.1.1", "@react-native-community/push-notification-ios": "^1.1.1",
"@react-native-community/slider": "^2.0.9", "@react-navigation/bottom-tabs": "^5.1.1",
"@react-navigation/bottom-tabs": "^5.3.2", "@react-navigation/drawer": "^5.1.1",
"@react-navigation/native": "^5.2.2", "@react-navigation/native": "^5.0.9",
"@react-navigation/stack": "^5.2.17", "@react-navigation/stack": "^5.1.1",
"i18n-js": "^3.3.0", "i18n-js": "^3.3.0",
"react": "~16.9.0", "react": "~16.9.0",
"react-dom": "16.9.0", "react-dom": "16.9.0",
@ -44,29 +43,29 @@
"react-native-linear-gradient": "^2.5.6", "react-native-linear-gradient": "^2.5.6",
"react-native-localize": "^1.4.0", "react-native-localize": "^1.4.0",
"react-native-modalize": "^1.3.6", "react-native-modalize": "^1.3.6",
"react-native-paper": "^3.9.0", "react-native-paper": "^3.8.0",
"react-native-permissions": "^2.1.4", "react-native-permissions": "^2.1.3",
"react-native-push-notification": "^3.3.1", "react-native-push-notification": "^3.3.0",
"react-native-reanimated": "^1.8.0", "react-native-reanimated": "~1.7.0",
"react-native-render-html": "^4.1.2", "react-native-render-html": "^4.1.2",
"react-native-safe-area-context": "0.7.3", "react-native-safe-area-context": "0.7.3",
"react-native-screens": "^2.7.0", "react-native-screens": "~2.2.0",
"react-native-splash-screen": "^3.2.0", "react-native-splash-screen": "^3.2.0",
"react-native-vector-icons": "^6.6.0", "react-native-vector-icons": "^6.6.0",
"react-native-webview": "^9.4.0", "react-native-web": "~0.11.7",
"react-native-webview": "8.1.1",
"react-navigation-collapsible": "^5.5.0", "react-navigation-collapsible": "^5.5.0",
"react-navigation-header-buttons": "^3.0.5" "react-navigation-header-buttons": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.6", "@babel/core": "^7.6.2",
"@babel/runtime": "^7.9.6", "@babel/runtime": "^7.6.2",
"@react-native-community/eslint-config": "^1.1.0", "@react-native-community/eslint-config": "^0.0.5",
"babel-jest": "^25.5.1", "babel-jest": "^24.9.0",
"eslint": "^6.5.1", "eslint": "^6.5.1",
"flow-bin": "^0.123.0", "jest": "^24.9.0",
"jest": "^25.5.3", "metro-react-native-babel-preset": "^0.58.0",
"jest-extended": "^0.11.5", "react-test-renderer": "16.9.0",
"metro-react-native-babel-preset": "^0.59.0", "flow-bin": "^0.122.0"
"react-test-renderer": "16.9.0"
} }
} }

View file

@ -12,13 +12,13 @@ type Props = {
title: string, title: string,
subtitle?: string, subtitle?: string,
left?: (props: { [keys: string]: any }) => React.Node, left?: (props: { [keys: string]: any }) => React.Node,
opened?: boolean, opened: boolean,
unmountWhenCollapsed: boolean, unmountWhenCollapsed: boolean,
children?: React.Node, children?: React.Node,
} }
type State = { type State = {
expanded: boolean, expanded: boolean
} }
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon); const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
@ -26,6 +26,7 @@ const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
class AnimatedAccordion extends React.Component<Props, State> { class AnimatedAccordion extends React.Component<Props, State> {
static defaultProps = { static defaultProps = {
opened: false,
unmountWhenCollapsed: false, unmountWhenCollapsed: false,
} }
chevronRef: { current: null | AnimatedListIcon }; chevronRef: { current: null | AnimatedListIcon };
@ -34,7 +35,7 @@ class AnimatedAccordion extends React.Component<Props, State> {
animEnd: string; animEnd: string;
state = { state = {
expanded: this.props.opened != null ? this.props.opened : false, expanded: this.props.opened,
} }
constructor(props) { constructor(props) {
@ -56,15 +57,14 @@ class AnimatedAccordion extends React.Component<Props, State> {
} }
toggleAccordion = () => { toggleAccordion = () => {
if (this.chevronRef.current != null) { if (this.chevronRef.current != null)
this.chevronRef.current.transitionTo({rotate: this.state.expanded ? this.animStart : this.animEnd}); this.chevronRef.current.transitionTo({rotate: this.state.expanded ? this.animStart : this.animEnd});
this.setState({expanded: !this.state.expanded}) this.setState({expanded: !this.state.expanded})
}
}; };
shouldComponentUpdate(nextProps: Props, nextState: State): boolean { shouldComponentUpdate(nextProps: Props) {
if (nextProps.opened != null && nextProps.opened !== this.props.opened)
this.state.expanded = nextProps.opened; this.state.expanded = nextProps.opened;
this.setupChevron();
return true; return true;
} }

View file

@ -3,14 +3,14 @@
import * as React from 'react'; import * as React from 'react';
import {Avatar, Card, List, withTheme} from 'react-native-paper'; import {Avatar, Card, List, withTheme} from 'react-native-paper';
import {StyleSheet, View} from "react-native"; import {StyleSheet, View} from "react-native";
import {DrawerNavigationProp} from "@react-navigation/drawer";
import type {CustomTheme} from "../../managers/ThemeManager"; import type {CustomTheme} from "../../managers/ThemeManager";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {StackNavigationProp} from "@react-navigation/stack";
const ICON_AMICALE = require("../../../assets/amicale.png"); const ICON_AMICALE = require("../../../assets/amicale.png");
type Props = { type Props = {
navigation: StackNavigationProp, navigation: DrawerNavigationProp,
theme: CustomTheme, theme: CustomTheme,
isLoggedIn: boolean, isLoggedIn: boolean,
} }

View file

@ -23,6 +23,10 @@ const LIST_ITEM_HEIGHT = 64;
class GroupListAccordion extends React.Component<Props> { class GroupListAccordion extends React.Component<Props> {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps: Props) { shouldComponentUpdate(nextProps: Props) {
return (nextProps.currentSearchString !== this.props.currentSearchString) return (nextProps.currentSearchString !== this.props.currentSearchString)
|| (nextProps.favoriteNumber !== this.props.favoriteNumber) || (nextProps.favoriteNumber !== this.props.favoriteNumber)
@ -32,6 +36,7 @@ class GroupListAccordion extends React.Component<Props> {
keyExtractor = (item: group) => item.id.toString(); keyExtractor = (item: group) => item.id.toString();
renderItem = ({item}: {item: group}) => { renderItem = ({item}: {item: group}) => {
if (stringMatchQuery(item.name, this.props.currentSearchString)) {
const onPress = () => this.props.onGroupPress(item); const onPress = () => this.props.onGroupPress(item);
const onStarPress = () => this.props.onFavoritePress(item); const onStarPress = () => this.props.onFavoritePress(item);
return ( return (
@ -41,21 +46,10 @@ class GroupListAccordion extends React.Component<Props> {
onPress={onPress} onPress={onPress}
onStarPress={onStarPress}/> onStarPress={onStarPress}/>
); );
} else
return null;
} }
getData() {
const originalData = this.props.item.content;
let displayData = [];
for (let i = 0; i < originalData.length; i++) {
if (stringMatchQuery(originalData[i].name, this.props.currentSearchString))
displayData.push(originalData[i]);
}
return displayData;
}
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
render() { render() {
const item = this.props.item; const item = this.props.item;
return ( return (
@ -79,14 +73,14 @@ class GroupListAccordion extends React.Component<Props> {
> >
{/*$FlowFixMe*/} {/*$FlowFixMe*/}
<FlatList <FlatList
data={this.getData()} data={item.content}
extraData={this.props.currentSearchString} extraData={this.props.currentSearchString}
renderItem={this.renderItem} renderItem={this.renderItem}
keyExtractor={this.keyExtractor} keyExtractor={this.keyExtractor}
listKey={item.id.toString()} listKey={item.id.toString()}
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
getItemLayout={this.itemLayout} // getItemLayout={this.itemLayout} // Broken with search
removeClippedSubviews={true} // removeClippedSubviews={true}
/> />
</AnimatedAccordion> </AnimatedAccordion>
</View> </View>

View file

@ -1,58 +0,0 @@
// @flow
import * as React from 'react';
import {Text, withTheme} from 'react-native-paper';
import {View} from "react-native-animatable";
import type {CustomTheme} from "../../managers/ThemeManager";
import Slider, {SliderProps} from "@react-native-community/slider";
type Props = {
theme: CustomTheme,
valueSuffix: string,
...SliderProps
}
type State = {
currentValue: number,
}
/**
* 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 {*}
*/
class CustomSlider extends React.Component<Props, State> {
static defaultProps = {
valueSuffix: "",
}
state = {
currentValue: this.props.value,
}
onValueChange = (value: number) => {
this.setState({currentValue: value});
if (this.props.onValueChange != null)
this.props.onValueChange(value);
}
render() {
return (
<View style={{flex: 1, flexDirection: 'row'}}>
<Text style={{marginHorizontal: 10, marginTop: 'auto', marginBottom: 'auto'}}>
{this.state.currentValue}min
</Text>
<Slider
{...this.props}
onValueChange={this.onValueChange}
/>
</View>
);
}
}
export default withTheme(CustomSlider);

View file

@ -31,16 +31,26 @@ class CustomTabBar extends React.Component<Props, State> {
static TAB_BAR_HEIGHT = 48; static TAB_BAR_HEIGHT = 48;
activeColor: string;
inactiveColor: string;
state = { state = {
translateY: new Animated.Value(0), translateY: new Animated.Value(0),
barSynced: false,// Is the bar synced with the header for animations? barSynced: false,// Is the bar synced with the header for animations?
} }
// shouldComponentUpdate(nextProps: Props): boolean {
// return (nextProps.theme.dark !== this.props.theme.dark)
// || (nextProps.state.index !== this.props.state.index);
// }
tabRef: Object; tabRef: Object;
constructor(props) { constructor(props) {
super(props); super(props);
this.tabRef = React.createRef(); this.tabRef = React.createRef();
this.activeColor = props.theme.colors.primary;
this.inactiveColor = props.theme.colors.tabIcon;
} }
onItemPress(route: Object, currentIndex: number, destIndex: number) { onItemPress(route: Object, currentIndex: number, destIndex: number) {
@ -109,7 +119,7 @@ class CustomTabBar extends React.Component<Props, State> {
} }
} }
const color = isFocused ? this.props.theme.colors.primary : this.props.theme.colors.tabIcon; const color = isFocused ? this.activeColor : this.inactiveColor;
if (route.name !== "home") { if (route.name !== "home") {
return <TabIcon return <TabIcon
onPress={onPress} onPress={onPress}

View file

@ -1,6 +1,6 @@
// @flow // @flow
import AsyncStorage from '@react-native-community/async-storage'; import {AsyncStorage} from "react-native";
/** /**
* Singleton used to manage preferences. * Singleton used to manage preferences.

View file

@ -25,6 +25,7 @@ const links = {
"Coucou !\n\n", "Coucou !\n\n",
yohanLinkedin: 'https://www.linkedin.com/in/yohan-simard', yohanLinkedin: 'https://www.linkedin.com/in/yohan-simard',
react: 'https://facebook.github.io/react-native/', react: 'https://facebook.github.io/react-native/',
expo: 'https://expo.io/',
}; };
type Props = { type Props = {
@ -152,6 +153,12 @@ class AboutScreen extends React.Component<Props, State> {
text: i18n.t('aboutScreen.reactNative'), text: i18n.t('aboutScreen.reactNative'),
showChevron: true showChevron: true
}, },
{
onPressCallback: () => openWebLink(links.react),
icon: 'language-javascript',
text: i18n.t('aboutScreen.expo'),
showChevron: true
},
{ {
onPressCallback: () => this.props.navigation.navigate('dependencies'), onPressCallback: () => this.props.navigation.navigate('dependencies'),
icon: 'developer-board', icon: 'developer-board',

View file

@ -44,7 +44,7 @@ class FeedItemScreen extends React.Component<Props> {
getHeaderButton = () => { getHeaderButton = () => {
return <MaterialHeaderButtons> return <MaterialHeaderButtons>
<Item title="main" iconName={'facebook'} color={"#2e88fe"} onPress={this.onOutLinkPress}/> <Item title="main" iconName={'facebook'} onPress={this.onOutLinkPress}/>
</MaterialHeaderButtons>; </MaterialHeaderButtons>;
}; };

View file

@ -17,6 +17,7 @@ import AnimatedFAB from "../../components/Animations/AnimatedFAB";
import {StackNavigationProp} from "@react-navigation/stack"; import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../managers/ThemeManager"; import type {CustomTheme} from "../../managers/ThemeManager";
import {View} from "react-native-animatable"; import {View} from "react-native-animatable";
import {HiddenItem} from "react-navigation-header-buttons";
import ConnectionManager from "../../managers/ConnectionManager"; import ConnectionManager from "../../managers/ConnectionManager";
import LogoutDialog from "../../components/Amicale/LogoutDialog"; import LogoutDialog from "../../components/Amicale/LogoutDialog";
// import DATA from "../dashboard_data.json"; // import DATA from "../dashboard_data.json";
@ -177,8 +178,8 @@ class HomeScreen extends React.Component<Props, State> {
const onPressAbout = () => this.props.navigation.navigate("about"); const onPressAbout = () => this.props.navigation.navigate("about");
return <MaterialHeaderButtons> return <MaterialHeaderButtons>
<Item title="log" iconName={logIcon} color={logColor} onPress={onPressLog}/> <Item title="log" iconName={logIcon} color={logColor} onPress={onPressLog}/>
<Item title={i18n.t("screens.settings")} iconName={"settings"} onPress={onPressSettings}/> <HiddenItem title={i18n.t("screens.settings")} iconName={"settings"} onPress={onPressSettings}/>
<Item title={i18n.t("screens.about")} iconName={"information"} onPress={onPressAbout}/> <HiddenItem title={i18n.t("screens.about")} iconName={"information"} onPress={onPressAbout}/>
</MaterialHeaderButtons>; </MaterialHeaderButtons>;
}; };

View file

@ -36,7 +36,6 @@ class FeedbackScreen extends React.Component<Props> {
}}> }}>
<Button <Button
icon="email" icon="email"
mode={"contained"}
style={{ style={{
marginLeft: 'auto', marginLeft: 'auto',
marginTop: 5, marginTop: 5,
@ -46,8 +45,6 @@ class FeedbackScreen extends React.Component<Props> {
</Button> </Button>
<Button <Button
icon="git" icon="git"
mode={"contained"}
color={"#609927"}
style={{ style={{
marginLeft: 'auto', marginLeft: 'auto',
marginTop: 5, marginTop: 5,
@ -57,8 +54,6 @@ class FeedbackScreen extends React.Component<Props> {
</Button> </Button>
<Button <Button
icon="facebook" icon="facebook"
mode={"contained"}
color={"#2e88fe"}
style={{ style={{
marginLeft: 'auto', marginLeft: 'auto',
marginTop: 5, marginTop: 5,

View file

@ -1,47 +1,39 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {ScrollView, View} from "react-native"; import {ScrollView} from "react-native";
import type {CustomTheme} from "../../managers/ThemeManager";
import ThemeManager from '../../managers/ThemeManager'; import ThemeManager from '../../managers/ThemeManager';
import i18n from "i18n-js"; import i18n from "i18n-js";
import AsyncStorageManager from "../../managers/AsyncStorageManager"; import AsyncStorageManager from "../../managers/AsyncStorageManager";
import {Card, List, Switch, ToggleButton, withTheme} from 'react-native-paper'; import {Card, List, Switch, ToggleButton} from 'react-native-paper';
import {Appearance} from "react-native-appearance"; import {Appearance} from "react-native-appearance";
import CustomSlider from "../../components/Overrides/CustomSlider"; import AnimatedAccordion from "../../components/Animations/AnimatedAccordion";
type Props = { type Props = {
theme: CustomTheme, navigation: Object,
}; };
type State = { type State = {
nightMode: boolean, nightMode: boolean,
nightModeFollowSystem: boolean, nightModeFollowSystem: boolean,
notificationReminderSelected: number, proxiwashNotifPickerSelected: string,
startScreenPickerSelected: string, startScreenPickerSelected: string,
}; };
/** /**
* Class defining the Settings screen. This screen shows controls to modify app preferences. * Class defining the Settings screen. This screen shows controls to modify app preferences.
*/ */
class SettingsScreen extends React.Component<Props, State> { export default class SettingsScreen extends React.Component<Props, State> {
state = {
savedNotificationReminder: number;
constructor() {
super();
let notifReminder = AsyncStorageManager.getInstance().preferences.proxiwashNotifications.current;
this.savedNotificationReminder = parseInt(notifReminder);
if (isNaN(this.savedNotificationReminder))
this.savedNotificationReminder = 0;
this.state = {
nightMode: ThemeManager.getNightMode(), nightMode: ThemeManager.getNightMode(),
nightModeFollowSystem: AsyncStorageManager.getInstance().preferences.nightModeFollowSystem.current === '1' && nightModeFollowSystem: AsyncStorageManager.getInstance().preferences.nightModeFollowSystem.current === '1' &&
Appearance.getColorScheme() !== 'no-preference', Appearance.getColorScheme() !== 'no-preference',
notificationReminderSelected: this.savedNotificationReminder, proxiwashNotifPickerSelected: AsyncStorageManager.getInstance().preferences.proxiwashNotifications.current,
startScreenPickerSelected: AsyncStorageManager.getInstance().preferences.defaultStartScreen.current, startScreenPickerSelected: AsyncStorageManager.getInstance().preferences.defaultStartScreen.current,
}; };
constructor() {
super();
} }
/** /**
@ -49,10 +41,14 @@ class SettingsScreen extends React.Component<Props, State> {
* *
* @param value The value to store * @param value The value to store
*/ */
onProxiwashNotifPickerValueChange = (value: number) => { onProxiwashNotifPickerValueChange = (value: string) => {
if (value != null) {
let key = AsyncStorageManager.getInstance().preferences.proxiwashNotifications.key; let key = AsyncStorageManager.getInstance().preferences.proxiwashNotifications.key;
AsyncStorageManager.getInstance().savePref(key, value.toString()); AsyncStorageManager.getInstance().savePref(key, value);
this.setState({notificationReminderSelected: value}) this.setState({
proxiwashNotifPickerSelected: value
});
}
}; };
/** /**
@ -77,16 +73,15 @@ class SettingsScreen extends React.Component<Props, State> {
*/ */
getProxiwashNotifPicker() { getProxiwashNotifPicker() {
return ( return (
<CustomSlider <ToggleButton.Row
style={{flex: 1, marginHorizontal: 10, height: 50}}
minimumValue={0}
maximumValue={10}
step={1}
value={this.savedNotificationReminder}
onValueChange={this.onProxiwashNotifPickerValueChange} onValueChange={this.onProxiwashNotifPickerValueChange}
thumbTintColor={this.props.theme.colors.primary} value={this.state.proxiwashNotifPickerSelected}
minimumTrackTintColor={this.props.theme.colors.primary} style={{marginLeft: 'auto', marginRight: 'auto'}}
/> >
<ToggleButton icon="close" value="never"/>
<ToggleButton icon="numeric-2" value="2"/>
<ToggleButton icon="numeric-5" value="5"/>
</ToggleButton.Row>
); );
} }
@ -138,7 +133,6 @@ class SettingsScreen extends React.Component<Props, State> {
* @param icon The icon name to display on the list item * @param icon The icon name to display on the list item
* @param title The text to display as this list item title * @param title The text to display as this list item title
* @param subtitle The text to display as this list item subtitle * @param subtitle The text to display as this list item subtitle
* @param state The current state of the switch
* @returns {React.Node} * @returns {React.Node}
*/ */
getToggleItem(onPressCallback: Function, icon: string, title: string, subtitle: string, state: boolean) { getToggleItem(onPressCallback: Function, icon: string, title: string, subtitle: string, state: boolean) {
@ -147,7 +141,7 @@ class SettingsScreen extends React.Component<Props, State> {
title={title} title={title}
description={subtitle} description={subtitle}
left={props => <List.Icon {...props} icon={icon}/>} left={props => <List.Icon {...props} icon={icon}/>}
right={() => right={props =>
<Switch <Switch
value={state} value={state}
onValueChange={onPressCallback} onValueChange={onPressCallback}
@ -183,31 +177,28 @@ class SettingsScreen extends React.Component<Props, State> {
this.state.nightMode this.state.nightMode
) : null ) : null
} }
<List.Item <AnimatedAccordion
title={i18n.t('settingsScreen.startScreen')} title={i18n.t('settingsScreen.startScreen')}
subtitle={i18n.t('settingsScreen.startScreenSub')} subtitle={i18n.t('settingsScreen.startScreenSub')}
left={props => <List.Icon {...props} icon="power"/>} left={props => <List.Icon {...props} icon="power"/>}
/> >
{this.getStartScreenPicker()} {this.getStartScreenPicker()}
</AnimatedAccordion>
</List.Section> </List.Section>
</Card> </Card>
<Card style={{margin: 5}}> <Card style={{margin: 5}}>
<Card.Title title="Proxiwash"/> <Card.Title title="Proxiwash"/>
<List.Section> <List.Section>
<List.Item <AnimatedAccordion
title={i18n.t('settingsScreen.proxiwashNotifReminder')} title={i18n.t('settingsScreen.proxiwashNotifReminder')}
description={i18n.t('settingsScreen.proxiwashNotifReminderSub')} description={i18n.t('settingsScreen.proxiwashNotifReminderSub')}
left={props => <List.Icon {...props} icon="washing-machine"/>} left={props => <List.Icon {...props} icon="washing-machine"/>}
opened={true} >
/>
<View style={{marginLeft: 30}}>
{this.getProxiwashNotifPicker()} {this.getProxiwashNotifPicker()}
</View> </AnimatedAccordion>
</List.Section> </List.Section>
</Card> </Card>
</ScrollView> </ScrollView>
); );
} }
} }
export default withTheme(SettingsScreen);

View file

@ -10,7 +10,7 @@ type Props = {
navigation: Object, navigation: Object,
}; };
const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/Proxiwash.png"; const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/proxiwash-logo.png";
/** /**
* Class defining the proxiwash about screen. * Class defining the proxiwash about screen.

View file

@ -18,7 +18,6 @@ import type {CustomTheme} from "../../managers/ThemeManager";
import {Collapsible} from "react-navigation-collapsible"; import {Collapsible} from "react-navigation-collapsible";
import {StackNavigationProp} from "@react-navigation/stack"; import {StackNavigationProp} from "@react-navigation/stack";
import {getCleanedMachineWatched, getMachineEndDate, isMachineWatched} from "../../utils/Proxiwash"; import {getCleanedMachineWatched, getMachineEndDate, isMachineWatched} from "../../utils/Proxiwash";
import {Modalize} from "react-native-modalize";
const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json"; const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json";
@ -56,12 +55,9 @@ type State = {
*/ */
class ProxiwashScreen extends React.Component<Props, State> { class ProxiwashScreen extends React.Component<Props, State> {
modalRef: null | Modalize; modalRef: Object;
fetchedData: { fetchedData: Object;
dryers: Array<Machine>,
washers: Array<Machine>,
};
state = { state = {
refreshing: false, refreshing: false,
@ -99,10 +95,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
*/ */
componentDidMount() { componentDidMount() {
this.props.navigation.setOptions({ this.props.navigation.setOptions({
headerRight: () => headerRight: this.getAboutButton,
<MaterialHeaderButtons>
<Item title="information" iconName="information" onPress={this.onAboutPress}/>
</MaterialHeaderButtons>,
}); });
} }
@ -112,6 +105,16 @@ class ProxiwashScreen extends React.Component<Props, State> {
*/ */
onAboutPress = () => this.props.navigation.navigate('proxiwash-about'); onAboutPress = () => this.props.navigation.navigate('proxiwash-about');
/**
* Gets the about header button
*
* @return {*}
*/
getAboutButton = () =>
<MaterialHeaderButtons>
<Item title="information" iconName="information" onPress={this.onAboutPress}/>
</MaterialHeaderButtons>;
/** /**
* Extracts the key for the given item * Extracts the key for the given item
* *
@ -120,6 +123,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
*/ */
getKeyExtractor = (item: Machine) => item.number; getKeyExtractor = (item: Machine) => item.number;
/** /**
* Setups notifications for the machine with the given ID. * Setups notifications for the machine with the given ID.
* One notification will be sent at the end of the program. * One notification will be sent at the end of the program.
@ -137,7 +141,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
this.showNotificationsDisabledWarning(); this.showNotificationsDisabledWarning();
}); });
} else { } else {
Notifications.setupMachineNotification(machine.number, false, null) Notifications.setupMachineNotification(machine.number, false)
.then(() => { .then(() => {
this.removeNotificationFromState(machine); this.removeNotificationFromState(machine);
}); });
@ -267,10 +271,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
let message = modalStateStrings[ProxiwashConstants.machineStates[item.state]]; let message = modalStateStrings[ProxiwashConstants.machineStates[item.state]];
const onPress = this.onSetupNotificationsPress.bind(this, item); const onPress = this.onSetupNotificationsPress.bind(this, item);
if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates["EN COURS"]) { if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates["EN COURS"]) {
let remainingTime = parseInt(item.remainingTime)
if (remainingTime < 0)
remainingTime = 0;
button = button =
{ {
text: isMachineWatched(item, this.state.machinesWatched) ? text: isMachineWatched(item, this.state.machinesWatched) ?
@ -284,7 +284,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
{ {
start: item.startTime, start: item.startTime,
end: item.endTime, end: item.endTime,
remaining: remainingTime remaining: item.remainingTime
}); });
} else if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates.DISPONIBLE) { } else if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates.DISPONIBLE) {
if (isDryer) if (isDryer)

View file

@ -10,7 +10,7 @@ type Props = {
navigation: Object, navigation: Object,
}; };
const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png"; const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/proximo-logo.png";
/** /**
* Class defining the proximo about screen. * Class defining the proximo about screen.

View file

@ -20,22 +20,17 @@ type Props = {
collapsibleStack: Collapsible, collapsibleStack: Collapsible,
theme: CustomTheme, theme: CustomTheme,
} }
const BIB_IMAGE = "https://scontent-cdg2-1.xx.fbcdn.net/v/t1.0-9/50695561_2124263197597162_2325349608210825216_n.jpg?_nc_cat=109&_nc_sid=8bfeb9&_nc_ohc=tmcV6FWO7_kAX9vfWHU&_nc_ht=scontent-cdg2-1.xx&oh=3b81c76e46b49f7c3a033ea3b07ec212&oe=5EC59B4D";
const RU_IMAGE = "https://scontent-cdg2-1.xx.fbcdn.net/v/t1.0-9/47123773_2041883702501779_5289372776166064128_o.jpg?_nc_cat=100&_nc_sid=cdbe9c&_nc_ohc=dpuBGlIIy_EAX8CyC0l&_nc_ht=scontent-cdg2-1.xx&oh=5c5bb4f0c7f12b554246f7c9b620a5f3&oe=5EC4DB31";
const ROOM_IMAGE = "https://scontent-cdt1-1.xx.fbcdn.net/v/t1.0-9/47041013_2043521689004647_316124496522117120_n.jpg?_nc_cat=103&_nc_sid=8bfeb9&_nc_ohc=bIp8OVJvvSEAX8mKnDZ&_nc_ht=scontent-cdt1-1.xx&oh=b4fef72a645804a849ad30e9e20fca12&oe=5EC29309";
const EMAIL_IMAGE = "https://etud-mel.insa-toulouse.fr/webmail/images/logo-bluemind.png";
const ENT_IMAGE = "https://ent.insa-toulouse.fr/media/org/jasig/portal/layout/tab-column/xhtml-theme/insa/institutional/LogoInsa.png";
const CLUBS_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png"; const PROXIMO_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/proximo-logo.png"
const PROFILE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ProfilAmicaliste.png"; const WIKETUD_LINK = "https://wiki.etud.insa-toulouse.fr/resources/assets/wiketud.png?ff051";
const VOTE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/";
const AMICALE_IMAGE = require("../../../assets/amicale.png"); const AMICALE_IMAGE = require("../../../assets/amicale.png");
const EE_IMAGE = "https://etud.insa-toulouse.fr/~eeinsat/wp-content/uploads/2019/09/logo-blanc.png";
const PROXIMO_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png" const TUTORINSA_IMAGE = "https://www.etud.insa-toulouse.fr/~tutorinsa/public/images/logo-gray.png";
const WIKETUD_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Wiketud.png";
const EE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/EEC.png";
const TUTORINSA_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/TutorINSA.png";
const BIB_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Bib.png";
const RU_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/RU.png";
const ROOM_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Salles.png";
const EMAIL_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Bluemind.png";
const ENT_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ENT.png";
export type listItem = { export type listItem = {
title: string, title: string,
@ -64,25 +59,19 @@ class ServicesScreen extends React.Component<Props, State> {
{ {
title: i18n.t('screens.clubsAbout'), title: i18n.t('screens.clubsAbout'),
subtitle: "CLUB LIST", subtitle: "CLUB LIST",
image: CLUBS_IMAGE, image: AMICALE_IMAGE,
onPress: () => nav.navigate("club-list"), onPress: () => nav.navigate("club-list"),
}, },
{ {
title: i18n.t('screens.profile'), title: i18n.t('screens.profile'),
subtitle: "PROFIL", subtitle: "PROFIL",
image: PROFILE_IMAGE,
onPress: () => nav.navigate("profile"),
},
{
title: i18n.t('screens.amicaleWebsite'),
subtitle: "AMICALE",
image: AMICALE_IMAGE, image: AMICALE_IMAGE,
onPress: () => nav.navigate("amicale-website"), onPress: () => nav.navigate("profile"),
}, },
{ {
title: i18n.t('screens.vote'), title: i18n.t('screens.vote'),
subtitle: "ELECTIONS", subtitle: "ELECTIONS",
image: VOTE_IMAGE, image: AMICALE_IMAGE,
onPress: () => nav.navigate("vote"), onPress: () => nav.navigate("vote"),
}, },
]; ];
@ -93,10 +82,16 @@ class ServicesScreen extends React.Component<Props, State> {
image: PROXIMO_IMAGE, image: PROXIMO_IMAGE,
onPress: () => nav.navigate("proximo"), onPress: () => nav.navigate("proximo"),
}, },
{
title: i18n.t('screens.amicaleWebsite'),
subtitle: "AMICALE",
image: AMICALE_IMAGE,
onPress: () => nav.navigate("amicale-website"),
},
{ {
title: "Wiketud", title: "Wiketud",
subtitle: "wiketud", subtitle: "wiketud",
image: WIKETUD_IMAGE, image: WIKETUD_LINK,
onPress: () => nav.navigate("wiketud"), onPress: () => nav.navigate("wiketud"),
}, },
{ {
@ -206,7 +201,7 @@ class ServicesScreen extends React.Component<Props, State> {
return <Avatar.Image return <Avatar.Image
{...props} {...props}
size={48} size={48}
source={source} source={AMICALE_IMAGE}
style={{backgroundColor: 'transparent'}} style={{backgroundColor: 'transparent'}}
/> />
else else

View file

@ -2,7 +2,6 @@
import {checkNotifications, requestNotifications, RESULTS} from 'react-native-permissions'; import {checkNotifications, requestNotifications, RESULTS} from 'react-native-permissions';
import AsyncStorageManager from "../managers/AsyncStorageManager"; import AsyncStorageManager from "../managers/AsyncStorageManager";
import i18n from "i18n-js";
const PushNotification = require("react-native-push-notification"); const PushNotification = require("react-native-push-notification");
@ -39,8 +38,8 @@ function createNotifications(machineID: string, date: Date) {
let reminderDate = new Date(date); let reminderDate = new Date(date);
reminderDate.setMinutes(reminderDate.getMinutes() - reminder); reminderDate.setMinutes(reminderDate.getMinutes() - reminder);
PushNotification.localNotificationSchedule({ PushNotification.localNotificationSchedule({
title: i18n.t("proxiwashScreen.notifications.machineRunningTitle", {time: reminder}), title: "Title",
message: i18n.t("proxiwashScreen.notifications.machineRunningBody", {number: machineID}), message: "Message",
id: id.toString(), id: id.toString(),
date: reminderDate, date: reminderDate,
}); });
@ -49,8 +48,8 @@ function createNotifications(machineID: string, date: Date) {
console.log("Setting up notifications for ", date); console.log("Setting up notifications for ", date);
PushNotification.localNotificationSchedule({ PushNotification.localNotificationSchedule({
title: i18n.t("proxiwashScreen.notifications.machineFinishedTitle"), title: "Title",
message: i18n.t("proxiwashScreen.notifications.machineFinishedBody", {number: machineID}), message: "Message",
id: machineID, id: machineID,
date: date, date: date,
}); });
@ -63,7 +62,7 @@ function createNotifications(machineID: string, date: Date) {
* @param isEnabled True to enable notifications, false to disable * @param isEnabled True to enable notifications, false to disable
* @param endDate * @param endDate
*/ */
export async function setupMachineNotification(machineID: string, isEnabled: boolean, endDate: Date | null) { export async function setupMachineNotification(machineID: string, isEnabled: boolean, endDate?: Date) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (isEnabled && endDate != null) { if (isEnabled && endDate != null) {
askPermissions() askPermissions()

View file

@ -1,41 +1,22 @@
// @flow // @flow
import type {Machine} from "../screens/Proxiwash/ProxiwashScreen"; import type {Machine} from "../screens/Proxiwash/ProxiwashScreen";
import ProxiwashConstants from "../constants/ProxiwashConstants";
/** export function getMachineEndDate(machine: Machine) {
* Gets the machine end Date object.
* If the end time is at least 12 hours before the current time,
* it will be considered as happening the day after.
* If it is before but less than 12 hours, it will be considered invalid (to fix proxiwash delay)
*
* @param machine The machine to get the date from
* @returns {Date} The date object representing the end time.
*/
export function getMachineEndDate(machine: Machine): Date | null {
const array = machine.endTime.split(":"); const array = machine.endTime.split(":");
let endDate = new Date(Date.now()); let date = new Date();
endDate.setHours(parseInt(array[0]), parseInt(array[1])); date.setHours(parseInt(array[0]), parseInt(array[1]));
if (date < new Date())
let limit = new Date(Date.now()); date.setDate(date.getDate() + 1);
if (endDate < limit) { return date;
if (limit.getHours() > 12) {
limit.setHours(limit.getHours() - 12);
if (endDate < limit)
endDate.setDate(endDate.getDate() + 1);
else
endDate = null;
} else
endDate = null;
}
return endDate;
} }
/** /**
* Checks whether the machine of the given ID has scheduled notifications * Checks whether the machine of the given ID has scheduled notifications
* *
* @param machine The machine to check * @param machine
* @param machineList The machine list * @param machineList
* @returns {boolean} * @returns {boolean}
*/ */
export function isMachineWatched(machine: Machine, machineList: Array<Machine>) { export function isMachineWatched(machine: Machine, machineList: Array<Machine>) {
@ -49,14 +30,7 @@ export function isMachineWatched(machine: Machine, machineList: Array<Machine>)
return watched; return watched;
} }
/** function getMachineOfId(id: string, allMachines: Array<Machine>) {
* Gets the machine of the given id
*
* @param id The machine's ID
* @param allMachines The machine list
* @returns {null|Machine} The machine or null if not found
*/
export function getMachineOfId(id: string, allMachines: Array<Machine>) {
for (let i = 0; i < allMachines.length; i++) { for (let i = 0; i < allMachines.length; i++) {
if (allMachines[i].number === id) if (allMachines[i].number === id)
return allMachines[i]; return allMachines[i];
@ -64,22 +38,13 @@ export function getMachineOfId(id: string, allMachines: Array<Machine>) {
return null; return null;
} }
/** export function getCleanedMachineWatched(machineList: Array<Machine>, allMachines: Array<Machine>) {
* Gets a cleaned machine watched list by removing invalid entries.
* An entry is considered invalid if the end time in the watched list
* and in the full list does not match (a new machine cycle started)
*
* @param machineWatchedList The current machine watch list
* @param allMachines The current full machine list
* @returns {Array<Machine>}
*/
export function getCleanedMachineWatched(machineWatchedList: Array<Machine>, allMachines: Array<Machine>) {
let newList = []; let newList = [];
for (let i = 0; i < machineWatchedList.length; i++) { for (let i = 0; i < machineList.length; i++) {
let machine = getMachineOfId(machineWatchedList[i].number, allMachines); let machine = getMachineOfId(machineList[i].number, allMachines);
if (machine !== null if (machine !== null
&& machineWatchedList[i].number === machine.number && machineList[i].number === machine.number && machineList[i].endTime === machine.endTime
&& machineWatchedList[i].endTime === machine.endTime) { && ProxiwashConstants.machineStates[machineList[i].state] === ProxiwashConstants.machineStates["EN COURS"]) {
newList.push(machine); newList.push(machine);
} }
} }