Compare commits
8 commits
1ede8f4e9a
...
8723765e43
| Author | SHA1 | Date | |
|---|---|---|---|
| 8723765e43 | |||
| ab554cae94 | |||
| ba893495e1 | |||
| 7829b893c5 | |||
| 557dd000ae | |||
| 2d26f2c395 | |||
| cacfb2862c | |||
| 17016b6452 |
24 changed files with 314 additions and 333 deletions
5
App.js
5
App.js
|
|
@ -14,6 +14,7 @@ import {initExpoToken} from "./utils/Notifications";
|
||||||
import {Provider as PaperProvider} from 'react-native-paper';
|
import {Provider as PaperProvider} from 'react-native-paper';
|
||||||
import AprilFoolsManager from "./managers/AprilFoolsManager";
|
import AprilFoolsManager from "./managers/AprilFoolsManager";
|
||||||
import Update from "./constants/Update";
|
import Update from "./constants/Update";
|
||||||
|
import ConnectionManager from "./managers/ConnectionManager";
|
||||||
|
|
||||||
type Props = {};
|
type Props = {};
|
||||||
|
|
||||||
|
|
@ -91,6 +92,10 @@ export default class App extends React.Component<Props, State> {
|
||||||
await AsyncStorageManager.getInstance().loadPreferences();
|
await AsyncStorageManager.getInstance().loadPreferences();
|
||||||
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
|
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
|
||||||
await initExpoToken();
|
await initExpoToken();
|
||||||
|
try {
|
||||||
|
await ConnectionManager.getInstance().recoverLogin();
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
this.onLoadFinished();
|
this.onLoadFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,19 @@ afterEach(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isLoggedIn yes', () => {
|
test('isLoggedIn yes', () => {
|
||||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
jest.spyOn(ConnectionManager.prototype, 'getToken').mockImplementationOnce(() => {
|
||||||
return Promise.resolve(true);
|
return 'token';
|
||||||
});
|
});
|
||||||
return expect(c.isLoggedIn()).resolves.toBe(true);
|
return expect(c.isLoggedIn()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('isLoggedIn no', () => {
|
test('isLoggedIn no', () => {
|
||||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
jest.spyOn(ConnectionManager.prototype, 'getToken').mockImplementationOnce(() => {
|
||||||
return Promise.reject(false);
|
return null;
|
||||||
});
|
});
|
||||||
return expect(c.isLoggedIn()).rejects.toBe(false);
|
return expect(c.isLoggedIn()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('recoverLogin error crypto', () => {
|
test('recoverLogin error crypto', () => {
|
||||||
jest.spyOn(SecureStore, 'getItemAsync').mockImplementationOnce(() => {
|
jest.spyOn(SecureStore, 'getItemAsync').mockImplementationOnce(() => {
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
|
|
@ -111,7 +110,12 @@ test("isConnectionResponseValid", () => {
|
||||||
state: false,
|
state: false,
|
||||||
};
|
};
|
||||||
expect(c.isConnectionResponseValid(json)).toBeTrue();
|
expect(c.isConnectionResponseValid(json)).toBeTrue();
|
||||||
|
json = {
|
||||||
|
state: false,
|
||||||
|
message: 'Adresse mail ou mot de passe incorrect',
|
||||||
|
token: ''
|
||||||
|
};
|
||||||
|
expect(c.isConnectionResponseValid(json)).toBeTrue();
|
||||||
json = {
|
json = {
|
||||||
state: true,
|
state: true,
|
||||||
message: 'Connexion confirmée',
|
message: 'Connexion confirmée',
|
||||||
|
|
@ -143,7 +147,6 @@ test("isConnectionResponseValid", () => {
|
||||||
expect(c.isConnectionResponseValid(json)).toBeFalse();
|
expect(c.isConnectionResponseValid(json)).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test("connect bad credentials", () => {
|
test("connect bad credentials", () => {
|
||||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
|
@ -178,6 +181,25 @@ test("connect good credentials", () => {
|
||||||
return expect(c.connect('email', 'password')).resolves.toBeTruthy();
|
return expect(c.connect('email', 'password')).resolves.toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("connect good credentials no consent", () => {
|
||||||
|
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
json: () => {
|
||||||
|
return {
|
||||||
|
state: false,
|
||||||
|
message: 'pas de consent',
|
||||||
|
token: '',
|
||||||
|
data: {
|
||||||
|
consent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return expect(c.connect('email', 'password'))
|
||||||
|
.rejects.toBe(ERROR_TYPE.NO_CONSENT);
|
||||||
|
});
|
||||||
|
|
||||||
test("connect good credentials, fail save token", () => {
|
test("connect good credentials, fail save token", () => {
|
||||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
|
@ -221,8 +243,8 @@ test("connect bogus response 1", () => {
|
||||||
|
|
||||||
|
|
||||||
test("authenticatedRequest success", () => {
|
test("authenticatedRequest success", () => {
|
||||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
jest.spyOn(ConnectionManager.prototype, 'getToken').mockImplementationOnce(() => {
|
||||||
return Promise.resolve('token');
|
return 'token';
|
||||||
});
|
});
|
||||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
|
@ -236,8 +258,8 @@ test("authenticatedRequest success", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("authenticatedRequest error wrong token", () => {
|
test("authenticatedRequest error wrong token", () => {
|
||||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
jest.spyOn(ConnectionManager.prototype, 'getToken').mockImplementationOnce(() => {
|
||||||
return Promise.resolve('token');
|
return 'token';
|
||||||
});
|
});
|
||||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
|
@ -251,8 +273,8 @@ test("authenticatedRequest error wrong token", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("authenticatedRequest error bogus response", () => {
|
test("authenticatedRequest error bogus response", () => {
|
||||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
jest.spyOn(ConnectionManager.prototype, 'getToken').mockImplementationOnce(() => {
|
||||||
return Promise.resolve('token');
|
return 'token';
|
||||||
});
|
});
|
||||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
|
@ -266,8 +288,8 @@ test("authenticatedRequest error bogus response", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("authenticatedRequest connection error", () => {
|
test("authenticatedRequest connection error", () => {
|
||||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
jest.spyOn(ConnectionManager.prototype, 'getToken').mockImplementationOnce(() => {
|
||||||
return Promise.resolve('token');
|
return 'token';
|
||||||
});
|
});
|
||||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||||
return Promise.reject()
|
return Promise.reject()
|
||||||
|
|
@ -277,8 +299,8 @@ test("authenticatedRequest connection error", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("authenticatedRequest error no token", () => {
|
test("authenticatedRequest error no token", () => {
|
||||||
jest.spyOn(ConnectionManager.prototype, 'recoverLogin').mockImplementationOnce(() => {
|
jest.spyOn(ConnectionManager.prototype, 'getToken').mockImplementationOnce(() => {
|
||||||
return Promise.reject(false);
|
return null;
|
||||||
});
|
});
|
||||||
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
loading: true,
|
loading: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
currentUserToken: string;
|
currentUserToken: string | null;
|
||||||
connectionManager: ConnectionManager;
|
connectionManager: ConnectionManager;
|
||||||
errorCode: number;
|
errorCode: number;
|
||||||
data: Object;
|
data: Object;
|
||||||
|
|
@ -35,8 +35,6 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
this.colors = props.theme.colors;
|
this.colors = props.theme.colors;
|
||||||
this.connectionManager = ConnectionManager.getInstance();
|
this.connectionManager = ConnectionManager.getInstance();
|
||||||
this.props.navigation.addListener('focus', this.onScreenFocus.bind(this));
|
this.props.navigation.addListener('focus', this.onScreenFocus.bind(this));
|
||||||
|
|
||||||
this.fetchData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onScreenFocus() {
|
onScreenFocus() {
|
||||||
|
|
@ -47,8 +45,7 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
fetchData = () => {
|
fetchData = () => {
|
||||||
if (!this.state.loading)
|
if (!this.state.loading)
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
this.connectionManager.isLoggedIn()
|
if (this.connectionManager.isLoggedIn()) {
|
||||||
.then(() => {
|
|
||||||
this.connectionManager.authenticatedRequest(this.props.link)
|
this.connectionManager.authenticatedRequest(this.props.link)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.onFinishedLoading(data, -1);
|
this.onFinishedLoading(data, -1);
|
||||||
|
|
@ -56,17 +53,16 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.onFinishedLoading(undefined, error);
|
this.onFinishedLoading(undefined, error);
|
||||||
});
|
});
|
||||||
})
|
} else {
|
||||||
.catch((error) => {
|
|
||||||
this.onFinishedLoading(undefined, ERROR_TYPE.BAD_CREDENTIALS);
|
this.onFinishedLoading(undefined, ERROR_TYPE.BAD_CREDENTIALS);
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onFinishedLoading(data: Object, error: number) {
|
onFinishedLoading(data: Object, error: number) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.currentUserToken = data !== undefined
|
this.currentUserToken = data !== undefined
|
||||||
? this.connectionManager.getToken()
|
? this.connectionManager.getToken()
|
||||||
: '';
|
: null;
|
||||||
this.errorCode = error;
|
this.errorCode = error;
|
||||||
this.setState({loading: false});
|
this.setState({loading: false});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ function HeaderButton(props) {
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={props.icon}
|
icon={props.icon}
|
||||||
size={26}
|
size={26}
|
||||||
color={colors.text}
|
color={props.color !== undefined ? props.color : colors.text}
|
||||||
onPress={props.onPress}
|
onPress={props.onPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Button, Card, withTheme} from 'react-native-paper';
|
import {Button, Card, withTheme} from 'react-native-paper';
|
||||||
import {StyleSheet} from "react-native";
|
import {StyleSheet} from "react-native";
|
||||||
|
import i18n from 'i18n-js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
navigation: Object,
|
navigation: Object,
|
||||||
|
|
@ -20,6 +21,8 @@ class ActionsDashBoardItem extends React.PureComponent<Props> {
|
||||||
|
|
||||||
openDrawer = () => this.props.navigation.openDrawer();
|
openDrawer = () => this.props.navigation.openDrawer();
|
||||||
|
|
||||||
|
gotToSettings = () => this.props.navigation.navigate("SettingsScreen");
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Card style={{
|
<Card style={{
|
||||||
|
|
@ -31,10 +34,17 @@ class ActionsDashBoardItem extends React.PureComponent<Props> {
|
||||||
icon="information"
|
icon="information"
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={this.openDrawer}
|
onPress={this.openDrawer}
|
||||||
style={styles.button}
|
style={styles.servicesButton}
|
||||||
>
|
>
|
||||||
PLUS DE SERVICES
|
{i18n.t("homeScreen.servicesButton")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
icon="settings"
|
||||||
|
mode="contained"
|
||||||
|
onPress={this.gotToSettings}
|
||||||
|
style={styles.settingsButton}
|
||||||
|
compact
|
||||||
|
/>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
@ -58,8 +68,12 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
button: {
|
servicesButton: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
|
marginRight: 5,
|
||||||
|
},
|
||||||
|
settingsButton: {
|
||||||
|
marginLeft: 5,
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
40
components/Lists/ProximoListItem.js
Normal file
40
components/Lists/ProximoListItem.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import {Avatar, List, Text, withTheme} from 'react-native-paper';
|
||||||
|
import i18n from "i18n-js";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onPress: Function,
|
||||||
|
color: string,
|
||||||
|
item: Object,
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProximoListItem extends React.PureComponent<Props> {
|
||||||
|
|
||||||
|
colors: Object;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.colors = props.theme.colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
title={this.props.item.name}
|
||||||
|
description={this.props.item.quantity + ' ' + i18n.t('proximoScreen.inStock')}
|
||||||
|
descriptionStyle={{color: this.props.color}}
|
||||||
|
onPress={this.props.onPress}
|
||||||
|
left={() => <Avatar.Image style={{backgroundColor: 'transparent'}} size={64}
|
||||||
|
source={{uri: this.props.item.image}}/>}
|
||||||
|
right={() =>
|
||||||
|
<Text style={{fontWeight: "bold"}}>
|
||||||
|
{this.props.item.price}€
|
||||||
|
</Text>}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withTheme(ProximoListItem);
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import {FlatList} from "react-native";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
data: Array<Object>,
|
|
||||||
keyExtractor: Function,
|
|
||||||
renderItem: Function,
|
|
||||||
updateData: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FlatList implementing PureComponent for increased performance.
|
|
||||||
*
|
|
||||||
* This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
|
|
||||||
* To force the component to update, change the value of updateData.
|
|
||||||
*/
|
|
||||||
export default class PureFlatList extends React.PureComponent<Props> {
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
updateData: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<FlatList
|
|
||||||
data={this.props.data}
|
|
||||||
keyExtractor={this.props.keyExtractor}
|
|
||||||
style={{minHeight: 300, width: '100%'}}
|
|
||||||
renderItem={this.props.renderItem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -57,8 +57,6 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
|
||||||
onFetchSuccess: Function;
|
onFetchSuccess: Function;
|
||||||
onFetchError: Function;
|
onFetchError: Function;
|
||||||
getEmptySectionHeader: Function;
|
getEmptySectionHeader: Function;
|
||||||
showSnackBar: Function;
|
|
||||||
hideSnackBar: Function;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -67,8 +65,6 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
|
||||||
this.onFetchSuccess = this.onFetchSuccess.bind(this);
|
this.onFetchSuccess = this.onFetchSuccess.bind(this);
|
||||||
this.onFetchError = this.onFetchError.bind(this);
|
this.onFetchError = this.onFetchError.bind(this);
|
||||||
this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
|
this.getEmptySectionHeader = this.getEmptySectionHeader.bind(this);
|
||||||
this.showSnackBar = this.showSnackBar.bind(this);
|
|
||||||
this.hideSnackBar = this.hideSnackBar.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -159,16 +155,12 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
|
||||||
/**
|
/**
|
||||||
* Shows the error popup
|
* Shows the error popup
|
||||||
*/
|
*/
|
||||||
showSnackBar() {
|
showSnackBar = () => this.setState({snackbarVisible: true});
|
||||||
this.setState({snackbarVisible: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the error popup
|
* Hides the error popup
|
||||||
*/
|
*/
|
||||||
hideSnackBar() {
|
hideSnackBar = () => this.setState({snackbarVisible: false});
|
||||||
this.setState({snackbarVisible: false})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let dataset = [];
|
let dataset = [];
|
||||||
|
|
@ -177,20 +169,10 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
|
||||||
const shouldRenderHeader = this.props.renderSectionHeader !== null;
|
const shouldRenderHeader = this.props.renderSectionHeader !== null;
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Snackbar
|
|
||||||
visible={this.state.snackbarVisible}
|
|
||||||
onDismiss={this.hideSnackBar}
|
|
||||||
action={{
|
|
||||||
label: 'OK',
|
|
||||||
onPress: this.hideSnackBar,
|
|
||||||
}}
|
|
||||||
duration={4000}
|
|
||||||
>
|
|
||||||
{i18n.t("homeScreen.listUpdateFail")}
|
|
||||||
</Snackbar>
|
|
||||||
{/*$FlowFixMe*/}
|
{/*$FlowFixMe*/}
|
||||||
<SectionList
|
<SectionList
|
||||||
sections={dataset}
|
sections={dataset}
|
||||||
|
extraData={this.props.updateData}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl
|
<RefreshControl
|
||||||
refreshing={this.state.refreshing}
|
refreshing={this.state.refreshing}
|
||||||
|
|
@ -212,6 +194,17 @@ export default class WebSectionList extends React.PureComponent<Props, State> {
|
||||||
onRefresh={this.onRefresh}/>
|
onRefresh={this.onRefresh}/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Snackbar
|
||||||
|
visible={this.state.snackbarVisible}
|
||||||
|
onDismiss={this.hideSnackBar}
|
||||||
|
action={{
|
||||||
|
label: 'OK',
|
||||||
|
onPress: () => {},
|
||||||
|
}}
|
||||||
|
duration={4000}
|
||||||
|
>
|
||||||
|
{i18n.t("homeScreen.listUpdateFail")}
|
||||||
|
</Snackbar>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
active: string,
|
|
||||||
isLoggedIn: boolean,
|
isLoggedIn: boolean,
|
||||||
dialogVisible: boolean,
|
dialogVisible: boolean,
|
||||||
};
|
};
|
||||||
|
|
@ -154,18 +153,16 @@ class SideBar extends React.PureComponent<Props, State> {
|
||||||
];
|
];
|
||||||
this.getRenderItem = this.getRenderItem.bind(this);
|
this.getRenderItem = this.getRenderItem.bind(this);
|
||||||
this.colors = props.theme.colors;
|
this.colors = props.theme.colors;
|
||||||
ConnectionManager.getInstance().setLoginCallback((value) => this.onLoginStateChange(value));
|
ConnectionManager.getInstance().addLoginStateListener((value) => this.onLoginStateChange(value));
|
||||||
this.state = {
|
this.state = {
|
||||||
active: 'Home',
|
isLoggedIn: ConnectionManager.getInstance().isLoggedIn(),
|
||||||
isLoggedIn: false,
|
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
};
|
};
|
||||||
ConnectionManager.getInstance().isLoggedIn().then(data => undefined).catch(error => undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showDisconnectDialog = () => this.setState({ dialogVisible: true });
|
showDisconnectDialog = () => this.setState({dialogVisible: true});
|
||||||
|
|
||||||
hideDisconnectDialog = () => this.setState({ dialogVisible: false });
|
hideDisconnectDialog = () => this.setState({dialogVisible: false});
|
||||||
|
|
||||||
|
|
||||||
onLoginStateChange(isLoggedIn: boolean) {
|
onLoginStateChange(isLoggedIn: boolean) {
|
||||||
|
|
@ -239,9 +236,10 @@ class SideBar extends React.PureComponent<Props, State> {
|
||||||
style={styles.drawerCover}
|
style={styles.drawerCover}
|
||||||
/>
|
/>
|
||||||
</TouchableRipple>
|
</TouchableRipple>
|
||||||
|
{/*$FlowFixMe*/}
|
||||||
<FlatList
|
<FlatList
|
||||||
data={this.dataSet}
|
data={this.dataSet}
|
||||||
extraData={this.state}
|
extraData={this.state.isLoggedIn}
|
||||||
keyExtractor={this.listKeyExtractor}
|
keyExtractor={this.listKeyExtractor}
|
||||||
renderItem={this.getRenderItem}
|
renderItem={this.getRenderItem}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,14 @@ export default class ConnectionManager {
|
||||||
static instance: ConnectionManager | null = null;
|
static instance: ConnectionManager | null = null;
|
||||||
|
|
||||||
#email: string;
|
#email: string;
|
||||||
#token: string;
|
#token: string | null;
|
||||||
|
|
||||||
loginCallback: Function;
|
listeners: Array<Function>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#token = null;
|
||||||
|
this.listeners = [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get this class instance or create one if none is found
|
* Get this class instance or create one if none is found
|
||||||
|
|
@ -35,20 +40,24 @@ export default class ConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoginStateChange(newState: boolean) {
|
onLoginStateChange(newState: boolean) {
|
||||||
this.loginCallback(newState);
|
for (let i = 0; i < this.listeners.length; i++) {
|
||||||
|
if (this.listeners[i] !== undefined)
|
||||||
|
this.listeners[i](newState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoginCallback(callback: Function) {
|
addLoginStateListener(listener: Function) {
|
||||||
this.loginCallback = callback;
|
this.listeners.push(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
async recoverLogin() {
|
async recoverLogin() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.#token !== undefined)
|
if (this.getToken() !== null)
|
||||||
resolve(this.#token);
|
resolve(this.getToken());
|
||||||
else {
|
else {
|
||||||
SecureStore.getItemAsync('token')
|
SecureStore.getItemAsync('token')
|
||||||
.then((token) => {
|
.then((token) => {
|
||||||
|
this.#token = token;
|
||||||
if (token !== null) {
|
if (token !== null) {
|
||||||
this.onLoginStateChange(true);
|
this.onLoginStateChange(true);
|
||||||
resolve(token);
|
resolve(token);
|
||||||
|
|
@ -62,16 +71,8 @@ export default class ConnectionManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async isLoggedIn() {
|
isLoggedIn() {
|
||||||
return new Promise((resolve, reject) => {
|
return this.getToken() !== null;
|
||||||
this.recoverLogin()
|
|
||||||
.then(() => {
|
|
||||||
resolve(true);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
reject(false);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveLogin(email: string, token: string) {
|
async saveLogin(email: string, token: string) {
|
||||||
|
|
@ -93,6 +94,7 @@ export default class ConnectionManager {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
SecureStore.deleteItemAsync('token')
|
SecureStore.deleteItemAsync('token')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
this.#token = null;
|
||||||
this.onLoginStateChange(false);
|
this.onLoginStateChange(false);
|
||||||
resolve(true);
|
resolve(true);
|
||||||
})
|
})
|
||||||
|
|
@ -126,7 +128,9 @@ export default class ConnectionManager {
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
reject(ERROR_TYPE.SAVE_TOKEN);
|
reject(ERROR_TYPE.SAVE_TOKEN);
|
||||||
});
|
});
|
||||||
} else if (data.data.consent !== undefined && !data.data.consent)
|
} else if (data.data !== undefined
|
||||||
|
&& data.data.consent !== undefined
|
||||||
|
&& !data.data.consent)
|
||||||
reject(ERROR_TYPE.NO_CONSENT);
|
reject(ERROR_TYPE.NO_CONSENT);
|
||||||
else
|
else
|
||||||
reject(ERROR_TYPE.BAD_CREDENTIALS);
|
reject(ERROR_TYPE.BAD_CREDENTIALS);
|
||||||
|
|
@ -166,15 +170,14 @@ export default class ConnectionManager {
|
||||||
|
|
||||||
async authenticatedRequest(url: string) {
|
async authenticatedRequest(url: string) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.recoverLogin()
|
if (this.getToken() !== null) {
|
||||||
.then(token => {
|
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}),
|
}),
|
||||||
body: JSON.stringify({token: token})
|
body: JSON.stringify({token: this.getToken()})
|
||||||
}).then(async (response) => response.json())
|
}).then(async (response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (this.isRequestResponseValid(data)) {
|
if (this.isRequestResponseValid(data)) {
|
||||||
|
|
@ -188,10 +191,8 @@ export default class ConnectionManager {
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
reject(ERROR_TYPE.CONNECTION_ERROR);
|
reject(ERROR_TYPE.CONNECTION_ERROR);
|
||||||
});
|
});
|
||||||
})
|
} else
|
||||||
.catch(() => {
|
|
||||||
reject(ERROR_TYPE.NO_TOKEN);
|
reject(ERROR_TYPE.NO_TOKEN);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -244,9 +244,7 @@ class AboutScreen extends React.Component<Props, State> {
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={this.appData}
|
data={this.appData}
|
||||||
extraData={this.state}
|
|
||||||
keyExtractor={this.keyExtractor}
|
keyExtractor={this.keyExtractor}
|
||||||
listKey={"app"}
|
|
||||||
renderItem={this.getCardItem}
|
renderItem={this.getCardItem}
|
||||||
/>
|
/>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
|
|
@ -269,17 +267,15 @@ class AboutScreen extends React.Component<Props, State> {
|
||||||
<Title>{i18n.t('aboutScreen.author')}</Title>
|
<Title>{i18n.t('aboutScreen.author')}</Title>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={this.authorData}
|
data={this.authorData}
|
||||||
extraData={this.state}
|
|
||||||
keyExtractor={this.keyExtractor}
|
keyExtractor={this.keyExtractor}
|
||||||
listKey={"team1"}
|
listKey={"1"}
|
||||||
renderItem={this.getCardItem}
|
renderItem={this.getCardItem}
|
||||||
/>
|
/>
|
||||||
<Title>{i18n.t('aboutScreen.additionalDev')}</Title>
|
<Title>{i18n.t('aboutScreen.additionalDev')}</Title>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={this.additionalDevData}
|
data={this.additionalDevData}
|
||||||
extraData={this.state}
|
|
||||||
keyExtractor={this.keyExtractor}
|
keyExtractor={this.keyExtractor}
|
||||||
listKey={"team2"}
|
listKey={"2"}
|
||||||
renderItem={this.getCardItem}
|
renderItem={this.getCardItem}
|
||||||
/>
|
/>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
|
|
@ -299,9 +295,7 @@ class AboutScreen extends React.Component<Props, State> {
|
||||||
<Title>{i18n.t('aboutScreen.technologies')}</Title>
|
<Title>{i18n.t('aboutScreen.technologies')}</Title>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={this.technoData}
|
data={this.technoData}
|
||||||
extraData={this.state}
|
|
||||||
keyExtractor={this.keyExtractor}
|
keyExtractor={this.keyExtractor}
|
||||||
listKey={"techno"}
|
|
||||||
renderItem={this.getCardItem}
|
renderItem={this.getCardItem}
|
||||||
/>
|
/>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
|
|
@ -404,28 +398,24 @@ class AboutScreen extends React.Component<Props, State> {
|
||||||
<Button
|
<Button
|
||||||
icon="email"
|
icon="email"
|
||||||
mode="contained"
|
mode="contained"
|
||||||
dark={true}
|
|
||||||
color={this.colors.primary}
|
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
}}
|
}}
|
||||||
onPress={this.onPressMail}>
|
onPress={this.onPressMail}>
|
||||||
<Text>{i18n.t('aboutScreen.bugsMail')}</Text>
|
{i18n.t('aboutScreen.bugsMail')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
icon="git"
|
icon="git"
|
||||||
mode="contained"
|
mode="contained"
|
||||||
dark={true}
|
|
||||||
color={this.colors.primary}
|
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
}}
|
}}
|
||||||
onPress={this.onPressGit}>
|
onPress={this.onPressGit}>
|
||||||
<Text>{i18n.t('aboutScreen.bugsGit')}</Text>
|
{i18n.t('aboutScreen.bugsGit')}
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
@ -476,8 +466,6 @@ class AboutScreen extends React.Component<Props, State> {
|
||||||
<FlatList
|
<FlatList
|
||||||
style={{padding: 5}}
|
style={{padding: 5}}
|
||||||
data={this.dataOrder}
|
data={this.dataOrder}
|
||||||
extraData={this.state}
|
|
||||||
keyExtractor={(item) => item.id}
|
|
||||||
renderItem={this.getMainCard}
|
renderItem={this.getMainCard}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ const emailRegex = /^.+@.+\..+$/;
|
||||||
class LoginScreen extends React.Component<Props, State> {
|
class LoginScreen extends React.Component<Props, State> {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
email: '',
|
email: 'vergnet@etud.insa-toulouse.fr',
|
||||||
password: '',
|
password: '3D514ùdsqg',
|
||||||
isEmailValidated: false,
|
isEmailValidated: false,
|
||||||
isPasswordValidated: false,
|
isPasswordValidated: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import PreviewEventDashboardItem from "../components/Home/PreviewEventDashboardI
|
||||||
import {stringToDate} from "../utils/Planning";
|
import {stringToDate} from "../utils/Planning";
|
||||||
import {openBrowser} from "../utils/WebBrowser";
|
import {openBrowser} from "../utils/WebBrowser";
|
||||||
import ActionsDashBoardItem from "../components/Home/ActionsDashboardItem";
|
import ActionsDashBoardItem from "../components/Home/ActionsDashboardItem";
|
||||||
|
import HeaderButton from "../components/Custom/HeaderButton";
|
||||||
|
import ConnectionManager from "../managers/ConnectionManager";
|
||||||
// import DATA from "../dashboard_data.json";
|
// import DATA from "../dashboard_data.json";
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,15 +32,10 @@ type Props = {
|
||||||
theme: Object,
|
theme: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
|
||||||
imageModalVisible: boolean,
|
|
||||||
imageList: Array<Object>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class defining the app's home screen
|
* Class defining the app's home screen
|
||||||
*/
|
*/
|
||||||
class HomeScreen extends React.Component<Props, State> {
|
class HomeScreen extends React.Component<Props> {
|
||||||
|
|
||||||
onProxiwashClick: Function;
|
onProxiwashClick: Function;
|
||||||
onTutorInsaClick: Function;
|
onTutorInsaClick: Function;
|
||||||
|
|
@ -49,10 +46,7 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
|
|
||||||
colors: Object;
|
colors: Object;
|
||||||
|
|
||||||
state = {
|
isLoggedIn: boolean | null;
|
||||||
imageModalVisible: false,
|
|
||||||
imageList: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
@ -63,6 +57,8 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
this.getRenderItem = this.getRenderItem.bind(this);
|
this.getRenderItem = this.getRenderItem.bind(this);
|
||||||
this.createDataset = this.createDataset.bind(this);
|
this.createDataset = this.createDataset.bind(this);
|
||||||
this.colors = props.theme.colors;
|
this.colors = props.theme.colors;
|
||||||
|
|
||||||
|
this.isLoggedIn = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -76,6 +72,35 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
return date.toLocaleString();
|
return date.toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
onScreenFocus = () => {
|
||||||
|
if (this.isLoggedIn !== ConnectionManager.getInstance().isLoggedIn()) {
|
||||||
|
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
||||||
|
this.props.navigation.setOptions({
|
||||||
|
headerRight: this.getHeaderButton,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
getHeaderButton = () => {
|
||||||
|
const screen = this.isLoggedIn
|
||||||
|
? "ProfileScreen"
|
||||||
|
: "LoginScreen";
|
||||||
|
const icon = this.isLoggedIn
|
||||||
|
? "account"
|
||||||
|
: "login";
|
||||||
|
const onPress = () => this.props.navigation.navigate(screen);
|
||||||
|
return <HeaderButton
|
||||||
|
icon={icon}
|
||||||
|
onPress={onPress}
|
||||||
|
color={this.isLoggedIn ? undefined : this.colors.primary}
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
|
||||||
onProxiwashClick() {
|
onProxiwashClick() {
|
||||||
this.props.navigation.navigate('Proxiwash');
|
this.props.navigation.navigate('Proxiwash');
|
||||||
}
|
}
|
||||||
|
|
@ -92,16 +117,6 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
this.props.navigation.navigate('SelfMenuScreen');
|
this.props.navigation.navigate('SelfMenuScreen');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract a key for the given item
|
|
||||||
*
|
|
||||||
* @param item The item to extract the key from
|
|
||||||
* @return {*} The extracted key
|
|
||||||
*/
|
|
||||||
getKeyExtractor(item: Object) {
|
|
||||||
return item !== undefined ? item.id : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the dataset to be used in the FlatList
|
* Creates the dataset to be used in the FlatList
|
||||||
*
|
*
|
||||||
|
|
@ -120,15 +135,11 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
data: dashboardData,
|
data: dashboardData,
|
||||||
extraData: super.state,
|
|
||||||
keyExtractor: this.getKeyExtractor,
|
|
||||||
id: SECTIONS_ID[0]
|
id: SECTIONS_ID[0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.t('homeScreen.newsFeed'),
|
title: i18n.t('homeScreen.newsFeed'),
|
||||||
data: newsData,
|
data: newsData,
|
||||||
extraData: super.state,
|
|
||||||
keyExtractor: this.getKeyExtractor,
|
|
||||||
id: SECTIONS_ID[1]
|
id: SECTIONS_ID[1]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -426,18 +437,6 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
openBrowser(link, this.colors.primary);
|
openBrowser(link, this.colors.primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
showImageModal(imageList) {
|
|
||||||
this.setState({
|
|
||||||
imageModalVisible: true,
|
|
||||||
imageList: imageList,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
hideImageModal = () => {
|
|
||||||
this.setState({imageModalVisible: false});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a render item for the given feed object
|
* Gets a render item for the given feed object
|
||||||
*
|
*
|
||||||
|
|
@ -472,7 +471,6 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
render() {
|
render() {
|
||||||
const nav = this.props.navigation;
|
const nav = this.props.navigation;
|
||||||
return (
|
return (
|
||||||
<View>
|
|
||||||
<WebSectionList
|
<WebSectionList
|
||||||
createDataset={this.createDataset}
|
createDataset={this.createDataset}
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
|
|
@ -480,8 +478,6 @@ class HomeScreen extends React.Component<Props, State> {
|
||||||
refreshOnFocus={true}
|
refreshOnFocus={true}
|
||||||
fetchUrl={DATA_URL}
|
fetchUrl={DATA_URL}
|
||||||
renderItem={this.getRenderItem}/>
|
renderItem={this.getRenderItem}/>
|
||||||
</View>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Image, Platform, ScrollView, View} from "react-native";
|
import {FlatList, Image, Platform, ScrollView, View} from "react-native";
|
||||||
import i18n from "i18n-js";
|
import i18n from "i18n-js";
|
||||||
import CustomModal from "../../components/Custom/CustomModal";
|
import CustomModal from "../../components/Custom/CustomModal";
|
||||||
import {Avatar, IconButton, List, RadioButton, Searchbar, Subheading, Text, Title, withTheme} from "react-native-paper";
|
import {IconButton, RadioButton, Searchbar, Subheading, Text, Title, withTheme} from "react-native-paper";
|
||||||
import PureFlatList from "../../components/Lists/PureFlatList";
|
import {stringMatchQuery} from "../../utils/Search";
|
||||||
|
import ProximoListItem from "../../components/Lists/ProximoListItem";
|
||||||
|
|
||||||
function sortPrice(a, b) {
|
function sortPrice(a, b) {
|
||||||
return a.price - b.price;
|
return a.price - b.price;
|
||||||
|
|
@ -39,7 +40,7 @@ type Props = {
|
||||||
type State = {
|
type State = {
|
||||||
currentSortMode: number,
|
currentSortMode: number,
|
||||||
modalCurrentDisplayItem: React.Node,
|
modalCurrentDisplayItem: React.Node,
|
||||||
currentlyDisplayedData: Array<Object>,
|
currentSearchString: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,30 +49,21 @@ type State = {
|
||||||
class ProximoListScreen extends React.Component<Props, State> {
|
class ProximoListScreen extends React.Component<Props, State> {
|
||||||
|
|
||||||
modalRef: Object;
|
modalRef: Object;
|
||||||
originalData: Array<Object>;
|
listData: Array<Object>;
|
||||||
shouldFocusSearchBar: boolean;
|
shouldFocusSearchBar: boolean;
|
||||||
|
|
||||||
onSearchStringChange: Function;
|
|
||||||
onSortMenuPress: Function;
|
|
||||||
renderItem: Function;
|
|
||||||
onModalRef: Function;
|
|
||||||
|
|
||||||
colors: Object;
|
colors: Object;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.originalData = this.props.route.params['data']['data'];
|
this.listData = this.props.route.params['data']['data'];
|
||||||
this.shouldFocusSearchBar = this.props.route.params['shouldFocusSearchBar'];
|
this.shouldFocusSearchBar = this.props.route.params['shouldFocusSearchBar'];
|
||||||
this.state = {
|
this.state = {
|
||||||
currentlyDisplayedData: this.originalData.sort(sortName),
|
currentSearchString: '',
|
||||||
currentSortMode: 3,
|
currentSortMode: 3,
|
||||||
modalCurrentDisplayItem: null,
|
modalCurrentDisplayItem: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onSearchStringChange = this.onSearchStringChange.bind(this);
|
|
||||||
this.onSortMenuPress = this.onSortMenuPress.bind(this);
|
|
||||||
this.renderItem = this.renderItem.bind(this);
|
|
||||||
this.onModalRef = this.onModalRef.bind(this);
|
|
||||||
this.colors = props.theme.colors;
|
this.colors = props.theme.colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,11 +72,9 @@ class ProximoListScreen extends React.Component<Props, State> {
|
||||||
* Creates the header content
|
* Creates the header content
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const button = this.getSortMenuButton.bind(this);
|
|
||||||
const title = this.getSearchBar.bind(this);
|
|
||||||
this.props.navigation.setOptions({
|
this.props.navigation.setOptions({
|
||||||
headerRight: button,
|
headerRight: this.getSortMenuButton,
|
||||||
headerTitle: title,
|
headerTitle: this.getSearchBar,
|
||||||
headerBackTitleVisible: false,
|
headerBackTitleVisible: false,
|
||||||
headerTitleContainerStyle: Platform.OS === 'ios' ?
|
headerTitleContainerStyle: Platform.OS === 'ios' ?
|
||||||
{marginHorizontal: 0, width: '70%'} :
|
{marginHorizontal: 0, width: '70%'} :
|
||||||
|
|
@ -97,21 +87,21 @@ class ProximoListScreen extends React.Component<Props, State> {
|
||||||
*
|
*
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
getSearchBar() {
|
getSearchBar = () => {
|
||||||
return (
|
return (
|
||||||
<Searchbar
|
<Searchbar
|
||||||
placeholder={i18n.t('proximoScreen.search')}
|
placeholder={i18n.t('proximoScreen.search')}
|
||||||
onChangeText={this.onSearchStringChange}
|
onChangeText={this.onSearchStringChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the sort menu header button
|
* Gets the sort menu header button
|
||||||
*
|
*
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
getSortMenuButton() {
|
getSortMenuButton = () => {
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="sort"
|
icon="sort"
|
||||||
|
|
@ -120,20 +110,20 @@ class ProximoListScreen extends React.Component<Props, State> {
|
||||||
onPress={this.onSortMenuPress}
|
onPress={this.onSortMenuPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used when clicking on the sort menu button.
|
* Callback used when clicking on the sort menu button.
|
||||||
* It will open the modal to show a sort selection
|
* It will open the modal to show a sort selection
|
||||||
*/
|
*/
|
||||||
onSortMenuPress() {
|
onSortMenuPress = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
modalCurrentDisplayItem: this.getModalSortMenu()
|
modalCurrentDisplayItem: this.getModalSortMenu()
|
||||||
});
|
});
|
||||||
if (this.modalRef) {
|
if (this.modalRef) {
|
||||||
this.modalRef.open();
|
this.modalRef.open();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current sort mode.
|
* Sets the current sort mode.
|
||||||
|
|
@ -144,19 +134,18 @@ class ProximoListScreen extends React.Component<Props, State> {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentSortMode: mode,
|
currentSortMode: mode,
|
||||||
});
|
});
|
||||||
let data = this.state.currentlyDisplayedData;
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 1:
|
case 1:
|
||||||
data.sort(sortPrice);
|
this.listData.sort(sortPrice);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
data.sort(sortPriceReverse);
|
this.listData.sort(sortPriceReverse);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
data.sort(sortName);
|
this.listData.sort(sortName);
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
data.sort(sortNameReverse);
|
this.listData.sort(sortNameReverse);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this.modalRef && mode !== this.state.currentSortMode) {
|
if (this.modalRef && mode !== this.state.currentSortMode) {
|
||||||
|
|
@ -181,46 +170,14 @@ class ProximoListScreen extends React.Component<Props, State> {
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitizes the given string to improve search performance
|
|
||||||
*
|
|
||||||
* @param str The string to sanitize
|
|
||||||
* @return {string} The sanitized string
|
|
||||||
*/
|
|
||||||
sanitizeString(str: string): string {
|
|
||||||
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns only articles whose name contains the given string.
|
|
||||||
* Case and accents insensitive.
|
|
||||||
*
|
|
||||||
* @param str The string used to filter article names
|
|
||||||
* @returns {[]}
|
|
||||||
*/
|
|
||||||
filterData(str: string) {
|
|
||||||
let filteredData = [];
|
|
||||||
const testStr = this.sanitizeString(str);
|
|
||||||
const articles = this.originalData;
|
|
||||||
for (const article of articles) {
|
|
||||||
const name = this.sanitizeString(article.name);
|
|
||||||
if (name.includes(testStr)) {
|
|
||||||
filteredData.push(article)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filteredData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used when the search changes
|
* Callback used when the search changes
|
||||||
*
|
*
|
||||||
* @param str The new search string
|
* @param str The new search string
|
||||||
*/
|
*/
|
||||||
onSearchStringChange(str: string) {
|
onSearchStringChange = (str: string) => {
|
||||||
this.setState({
|
this.setState({currentSearchString: str})
|
||||||
currentlyDisplayedData: this.filterData(str)
|
};
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the modal content depending on the given article
|
* Gets the modal content depending on the given article
|
||||||
|
|
@ -333,23 +290,20 @@ class ProximoListScreen extends React.Component<Props, State> {
|
||||||
* @param item The article to render
|
* @param item The article to render
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
renderItem({item}: Object) {
|
renderItem = ({item}: Object) => {
|
||||||
|
if (stringMatchQuery(item.name, this.state.currentSearchString)) {
|
||||||
const onPress = this.onListItemPress.bind(this, item);
|
const onPress = this.onListItemPress.bind(this, item);
|
||||||
|
const color = this.getStockColor(parseInt(item.quantity));
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<ProximoListItem
|
||||||
title={item.name}
|
item={item}
|
||||||
description={item.quantity + ' ' + i18n.t('proximoScreen.inStock')}
|
|
||||||
descriptionStyle={{color: this.getStockColor(parseInt(item.quantity))}}
|
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
left={() => <Avatar.Image style={{backgroundColor: 'transparent'}} size={64}
|
color={color}
|
||||||
source={{uri: item.image}}/>}
|
|
||||||
right={() =>
|
|
||||||
<Text style={{fontWeight: "bold"}}>
|
|
||||||
{item.price}€
|
|
||||||
</Text>}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
} else
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts a key for the given article
|
* Extracts a key for the given article
|
||||||
|
|
@ -366,9 +320,9 @@ class ProximoListScreen extends React.Component<Props, State> {
|
||||||
*
|
*
|
||||||
* @param ref
|
* @param ref
|
||||||
*/
|
*/
|
||||||
onModalRef(ref: Object) {
|
onModalRef = (ref: Object) => {
|
||||||
this.modalRef = ref;
|
this.modalRef = ref;
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -378,11 +332,12 @@ class ProximoListScreen extends React.Component<Props, State> {
|
||||||
<CustomModal onRef={this.onModalRef}>
|
<CustomModal onRef={this.onModalRef}>
|
||||||
{this.state.modalCurrentDisplayItem}
|
{this.state.modalCurrentDisplayItem}
|
||||||
</CustomModal>
|
</CustomModal>
|
||||||
<PureFlatList
|
{/*$FlowFixMe*/}
|
||||||
data={this.state.currentlyDisplayedData}
|
<FlatList
|
||||||
|
data={this.listData}
|
||||||
|
extraData={this.state.currentSearchString + this.state.currentSortMode}
|
||||||
keyExtractor={this.keyExtractor}
|
keyExtractor={this.keyExtractor}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
updateData={this.state.currentSortMode}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,6 @@ class ProximoMainScreen extends React.Component<Props, State> {
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
data: this.generateData(fetchedData),
|
data: this.generateData(fetchedData),
|
||||||
extraData: this.state,
|
|
||||||
keyExtractor: this.getKeyExtractor
|
keyExtractor: this.getKeyExtractor
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -272,14 +272,12 @@ class ProxiwashScreen extends React.Component<Props, State> {
|
||||||
title: i18n.t('proxiwashScreen.dryers'),
|
title: i18n.t('proxiwashScreen.dryers'),
|
||||||
icon: 'tumble-dryer',
|
icon: 'tumble-dryer',
|
||||||
data: data.dryers === undefined ? [] : data.dryers,
|
data: data.dryers === undefined ? [] : data.dryers,
|
||||||
extraData: this.state,
|
|
||||||
keyExtractor: this.getKeyExtractor
|
keyExtractor: this.getKeyExtractor
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.t('proxiwashScreen.washers'),
|
title: i18n.t('proxiwashScreen.washers'),
|
||||||
icon: 'washing-machine',
|
icon: 'washing-machine',
|
||||||
data: data.washers === undefined ? [] : data.washers,
|
data: data.washers === undefined ? [] : data.washers,
|
||||||
extraData: this.state,
|
|
||||||
keyExtractor: this.getKeyExtractor
|
keyExtractor: this.getKeyExtractor
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ class SelfMenuScreen extends React.Component<Props> {
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
data: [],
|
data: [],
|
||||||
extraData: super.state,
|
|
||||||
keyExtractor: this.getKeyExtractor
|
keyExtractor: this.getKeyExtractor
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -69,7 +68,6 @@ class SelfMenuScreen extends React.Component<Props> {
|
||||||
{
|
{
|
||||||
title: DateManager.getInstance().getTranslatedDate(fetchedData[i].date),
|
title: DateManager.getInstance().getTranslatedDate(fetchedData[i].date),
|
||||||
data: fetchedData[i].meal[0].foodcategory,
|
data: fetchedData[i].meal[0].foodcategory,
|
||||||
extraData: super.state,
|
|
||||||
keyExtractor: this.getKeyExtractor,
|
keyExtractor: this.getKeyExtractor,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import ScoreManager from "./ScoreManager";
|
||||||
import type {coordinates} from './Shapes/BaseShape';
|
import type {coordinates} from './Shapes/BaseShape';
|
||||||
|
|
||||||
|
|
||||||
export type cell = {color: string, isEmpty: boolean};
|
export type cell = {color: string, isEmpty: boolean, key: string};
|
||||||
export type grid = Array<Array<cell>>;
|
export type grid = Array<Array<cell>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,6 +50,7 @@ export default class GridManager {
|
||||||
line.push({
|
line.push({
|
||||||
color: this.#colors.tetrisBackground,
|
color: this.#colors.tetrisBackground,
|
||||||
isEmpty: true,
|
isEmpty: true,
|
||||||
|
key: col.toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return line;
|
return line;
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ export default class Piece {
|
||||||
grid[coord[i].y][coord[i].x] = {
|
grid[coord[i].y][coord[i].x] = {
|
||||||
color: this.#colors.tetrisBackground,
|
color: this.#colors.tetrisBackground,
|
||||||
isEmpty: true,
|
isEmpty: true,
|
||||||
|
key: grid[coord[i].y][coord[i].x].key
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +74,7 @@ export default class Piece {
|
||||||
grid[coord[i].y][coord[i].x] = {
|
grid[coord[i].y][coord[i].x] = {
|
||||||
color: this.#currentShape.getColor(),
|
color: this.#currentShape.getColor(),
|
||||||
isEmpty: false,
|
isEmpty: false,
|
||||||
|
key: grid[coord[i].y][coord[i].x].key
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@ import {View} from 'react-native';
|
||||||
import {withTheme} from 'react-native-paper';
|
import {withTheme} from 'react-native-paper';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
color: string,
|
item: Object
|
||||||
isEmpty: boolean,
|
|
||||||
id: string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Cell extends React.PureComponent<Props> {
|
class Cell extends React.PureComponent<Props> {
|
||||||
|
|
@ -20,17 +18,19 @@ class Cell extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const item = this.props.item;
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: this.props.isEmpty ? 'transparent' : this.props.color,
|
backgroundColor: item.isEmpty ? 'transparent' : item.color,
|
||||||
borderColor: this.props.isEmpty ? 'transparent' : this.colors.tetrisBorder,
|
borderColor: item.isEmpty ? 'transparent' : this.colors.tetrisBorder,
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
}}
|
}}
|
||||||
|
key={item.key}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@ type Props = {
|
||||||
backgroundColor: string,
|
backgroundColor: string,
|
||||||
height: number,
|
height: number,
|
||||||
width: number,
|
width: number,
|
||||||
containerMaxHeight: number|string,
|
containerMaxHeight: number | string,
|
||||||
containerMaxWidth: number|string,
|
containerMaxWidth: number | string,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Grid extends React.Component<Props>{
|
class Grid extends React.Component<Props> {
|
||||||
|
|
||||||
colors: Object;
|
colors: Object;
|
||||||
|
|
||||||
|
|
@ -25,22 +25,24 @@ class Grid extends React.Component<Props>{
|
||||||
}
|
}
|
||||||
|
|
||||||
getRow(rowNumber: number) {
|
getRow(rowNumber: number) {
|
||||||
let cells = [];
|
let cells = this.props.grid[rowNumber].map(this.getCellRender);
|
||||||
for (let i = 0; i < this.props.width; i++) {
|
return (
|
||||||
let cell = this.props.grid[rowNumber][i];
|
<View
|
||||||
let key = rowNumber + ':' + i;
|
style={{
|
||||||
cells.push(<Cell color={cell.color} isEmpty={cell.isEmpty} id={key}/>);
|
|
||||||
}
|
|
||||||
return(
|
|
||||||
<View style={{
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
backgroundColor: this.props.backgroundColor,
|
backgroundColor: this.props.backgroundColor,
|
||||||
}}>
|
}}
|
||||||
|
key={rowNumber.toString()}
|
||||||
|
>
|
||||||
{cells}
|
{cells}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCellRender = (item: Object) => {
|
||||||
|
return <Cell item={item} key={item.key}/>;
|
||||||
|
};
|
||||||
|
|
||||||
getGrid() {
|
getGrid() {
|
||||||
let rows = [];
|
let rows = [];
|
||||||
for (let i = 0; i < this.props.height; i++) {
|
for (let i = 0; i < this.props.height; i++) {
|
||||||
|
|
@ -55,7 +57,7 @@ class Grid extends React.Component<Props>{
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
maxWidth: this.props.containerMaxWidth,
|
maxWidth: this.props.containerMaxWidth,
|
||||||
maxHeight: this.props.containerMaxHeight,
|
maxHeight: this.props.containerMaxHeight,
|
||||||
aspectRatio: this.props.width/this.props.height,
|
aspectRatio: this.props.width / this.props.height,
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
}}>
|
}}>
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,24 @@ class Preview extends React.PureComponent<Props> {
|
||||||
let grids = [];
|
let grids = [];
|
||||||
for (let i = 0; i < this.props.next.length; i++) {
|
for (let i = 0; i < this.props.next.length; i++) {
|
||||||
grids.push(
|
grids.push(
|
||||||
<Grid
|
this.getGridRender(this.props.next[i], i)
|
||||||
width={this.props.next[i][0].length}
|
|
||||||
height={this.props.next[i].length}
|
|
||||||
grid={this.props.next[i]}
|
|
||||||
containerMaxHeight={50}
|
|
||||||
containerMaxWidth={50}
|
|
||||||
backgroundColor={'transparent'}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return grids;
|
return grids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGridRender(item: Object, index: number) {
|
||||||
|
return <Grid
|
||||||
|
width={item[0].length}
|
||||||
|
height={item.length}
|
||||||
|
grid={item}
|
||||||
|
containerMaxHeight={50}
|
||||||
|
containerMaxWidth={50}
|
||||||
|
backgroundColor={'transparent'}
|
||||||
|
key={index.toString()}
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.next.length > 0) {
|
if (this.props.next.length > 0) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@
|
||||||
"homeScreen": {
|
"homeScreen": {
|
||||||
"listUpdated": "List updated!",
|
"listUpdated": "List updated!",
|
||||||
"listUpdateFail": "Error while updating list",
|
"listUpdateFail": "Error while updating list",
|
||||||
|
"servicesButton": "More services",
|
||||||
"newsFeed": "Campus News",
|
"newsFeed": "Campus News",
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"seeMore": "Click to see more",
|
"seeMore": "Click to see more",
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@
|
||||||
"homeScreen": {
|
"homeScreen": {
|
||||||
"listUpdated": "List mise à jour!",
|
"listUpdated": "List mise à jour!",
|
||||||
"listUpdateFail": "Erreur lors de la mise à jour de la liste",
|
"listUpdateFail": "Erreur lors de la mise à jour de la liste",
|
||||||
|
"servicesButton": "Plus de services",
|
||||||
"newsFeed": "Nouvelles du campus",
|
"newsFeed": "Nouvelles du campus",
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"seeMore": "Cliquez pour plus d'infos",
|
"seeMore": "Cliquez pour plus d'infos",
|
||||||
|
|
@ -159,7 +160,7 @@
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"description": "C'est le service de laverie proposé par promologis pour les résidences INSA (On t'en voudra pas si tu loges pas sur le campus et que tu fais ta machine ici). Le local situé au pied du R2 avec ses 3 sèche-linges et 9 machines est ouvert 7J/7 24h/24 ! Ici tu peux vérifier leur disponibilité ! Tu peux amener ta lessive, la prendre sur place ou encore mieux l'acheter au Proximo (moins chère qu'à la laverie directement). Tu peux payer par CB ou espèces.",
|
"description": "C'est le service de laverie proposé par promologis pour les résidences INSA (On t'en voudra pas si tu loges pas sur le campus et que tu fais ta machine ici). Le local situé au pied du R2 avec ses 3 sèche-linges et 9 machines est ouvert 7J/7 24h/24 ! Ici tu peux vérifier leur disponibilité ! Tu peux amener ta lessive, la prendre sur place ou encore mieux l'acheter au Proximo (moins chère qu'à la laverie directement). Tu peux payer par CB ou espèces.",
|
||||||
"informationTab": "Informations",
|
"informationTab": "Informations",
|
||||||
"paymentTab" : "Paiement",
|
"paymentTab": "Paiement",
|
||||||
"tariffs": "Tarifs",
|
"tariffs": "Tarifs",
|
||||||
"washersTariff": "3€ la machine + 0.80€ avec la lessive.",
|
"washersTariff": "3€ la machine + 0.80€ avec la lessive.",
|
||||||
"dryersTariff": "0.35€ pour 5min de sèche linge.",
|
"dryersTariff": "0.35€ pour 5min de sèche linge.",
|
||||||
|
|
@ -186,7 +187,6 @@
|
||||||
"error": "Il y a eu une erreur et il est impossible de récupérer les informations de cette machine. Veuillez nous excuser pour le gène occasionnée.",
|
"error": "Il y a eu une erreur et il est impossible de récupérer les informations de cette machine. Veuillez nous excuser pour le gène occasionnée.",
|
||||||
"notificationErrorTitle": "Erreur",
|
"notificationErrorTitle": "Erreur",
|
||||||
"notificationErrorDescription": "Impossible de créer les notifications. Merci de vérifier que vous avez activé les notifications puis redémarrez l'appli."
|
"notificationErrorDescription": "Impossible de créer les notifications. Merci de vérifier que vous avez activé les notifications puis redémarrez l'appli."
|
||||||
|
|
||||||
},
|
},
|
||||||
"states": {
|
"states": {
|
||||||
"finished": "TERMINÉ",
|
"finished": "TERMINÉ",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue