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,13 +153,11 @@ 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});
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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++) {
|
|
||||||
let cell = this.props.grid[rowNumber][i];
|
|
||||||
let key = rowNumber + ':' + i;
|
|
||||||
cells.push(<Cell color={cell.color} isEmpty={cell.isEmpty} id={key}/>);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<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++) {
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
@ -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