Compare commits

...

31 commits

Author SHA1 Message Date
ac9709d666 Updated translations 2020-04-22 18:54:34 +02:00
f7e66b1251 Reduced splash size 2020-04-22 18:46:54 +02:00
38a5761f23 Removed drawer related files and renamed navigator for more coherence 2020-04-22 18:36:57 +02:00
faa174b8f1 Moved proximo and proxiwash images on the web 2020-04-22 18:34:23 +02:00
030cf7b795 Fixed text alignement 2020-04-22 15:20:10 +02:00
e8fc8b79dc Updated translations 2020-04-22 15:19:00 +02:00
62db99cac7 Reduced image size 2020-04-22 14:58:08 +02:00
87f0c01024 Redirect to service screen index 2020-04-22 12:29:36 +02:00
b5d2f686dd Fixed image alignement 2020-04-22 12:28:35 +02:00
9207d02c2a Improved dashboard amicale item 2020-04-22 12:20:55 +02:00
b31269586b Use card items when viewing the section 2020-04-22 12:08:23 +02:00
fc7754588f Improved login flow 2020-04-22 11:49:22 +02:00
92fce1d425 Fixed login redirection error 2020-04-22 11:35:01 +02:00
0c71a78b22 Enabled collapsing header in login screen 2020-04-22 11:18:43 +02:00
c9b8a6e2ca Added login/logout icon on home 2020-04-22 11:12:24 +02:00
3f945fca7a Added services section title to match current section 2020-04-22 10:49:54 +02:00
49fa8a82e3 Improved readability 2020-04-22 10:47:18 +02:00
524dd5362a Changed sections icons 2020-04-22 10:45:31 +02:00
443f179f1d Changed cards to basic view 2020-04-22 10:42:02 +02:00
8eaa46b900 Added amicale section 2020-04-22 10:14:37 +02:00
926515213d Improved code readability 2020-04-22 09:54:40 +02:00
bbe343da3b Render services in sections 2020-04-22 09:37:31 +02:00
8fca2eac12 Moved tab order 2020-04-22 08:50:47 +02:00
20cff1aeb1 Fixed ref warning 2020-04-21 20:05:21 +02:00
96ed75ac72 Added collapsible header to profile 2020-04-21 20:02:17 +02:00
b151a8ff6f Updated translations and fixed filenames 2020-04-21 19:54:53 +02:00
1f0ada3b24 Improved project structure 2020-04-21 19:48:44 +02:00
78634b0c5d Added more websites 2020-04-21 19:46:33 +02:00
e6835b0d6f Improved dashboard interaction 2020-04-21 19:40:40 +02:00
03c4a43e58 Moved all services in same tab and planning in its own 2020-04-21 19:30:13 +02:00
0b17a35856 Improved selection screens 2020-04-21 15:17:00 +02:00
30 changed files with 839 additions and 839 deletions

6
App.js
View file

@ -9,7 +9,7 @@ import {AppLoading} from 'expo';
import type {CustomTheme} from "./src/managers/ThemeManager"; import type {CustomTheme} from "./src/managers/ThemeManager";
import ThemeManager from './src/managers/ThemeManager'; import ThemeManager from './src/managers/ThemeManager';
import {NavigationContainer} from '@react-navigation/native'; import {NavigationContainer} from '@react-navigation/native';
import DrawerNavigator from './src/navigation/DrawerNavigator'; import MainNavigator from './src/navigation/MainNavigator';
import {initExpoToken} from "./src/utils/Notifications"; import {initExpoToken} from "./src/utils/Notifications";
import {Provider as PaperProvider} from 'react-native-paper'; import {Provider as PaperProvider} from 'react-native-paper';
import AprilFoolsManager from "./src/managers/AprilFoolsManager"; import AprilFoolsManager from "./src/managers/AprilFoolsManager";
@ -156,7 +156,7 @@ export default class App extends React.Component<Props, State> {
*/ */
onLoadFinished() { onLoadFinished() {
// Only show intro if this is the first time starting the app // Only show intro if this is the first time starting the app
this.createDrawerNavigator = () => <DrawerNavigator this.createDrawerNavigator = () => <MainNavigator
defaultHomeRoute={this.defaultHomeRoute} defaultHomeRoute={this.defaultHomeRoute}
defaultHomeData={this.defaultHomeData} defaultHomeData={this.defaultHomeData}
/>; />;
@ -191,7 +191,7 @@ export default class App extends React.Component<Props, State> {
return ( return (
<PaperProvider theme={this.state.currentTheme}> <PaperProvider theme={this.state.currentTheme}>
<NavigationContainer theme={this.state.currentTheme} ref={this.navigatorRef}> <NavigationContainer theme={this.state.currentTheme} ref={this.navigatorRef}>
<DrawerNavigator <MainNavigator
defaultHomeRoute={this.defaultHomeRoute} defaultHomeRoute={this.defaultHomeRoute}
defaultHomeData={this.defaultHomeData} defaultHomeData={this.defaultHomeData}
/> />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View file

@ -5,36 +5,48 @@ import {Avatar, Card, List, withTheme} from 'react-native-paper';
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import {DrawerNavigationProp} from "@react-navigation/drawer"; import {DrawerNavigationProp} from "@react-navigation/drawer";
import type {CustomTheme} from "../../managers/ThemeManager"; import type {CustomTheme} from "../../managers/ThemeManager";
import i18n from 'i18n-js';
const ICON_AMICALE = require("../../../assets/amicale.png"); const ICON_AMICALE = require("../../../assets/amicale.png");
type Props = { type Props = {
navigation: DrawerNavigationProp, navigation: DrawerNavigationProp,
theme: CustomTheme, theme: CustomTheme,
isLoggedIn: boolean,
} }
class ActionsDashBoardItem extends React.Component<Props> { class ActionsDashBoardItem extends React.Component<Props> {
shouldComponentUpdate(nextProps: Props): boolean { shouldComponentUpdate(nextProps: Props): boolean {
return (nextProps.theme.dark !== this.props.theme.dark) return (nextProps.theme.dark !== this.props.theme.dark)
|| (nextProps.isLoggedIn !== this.props.isLoggedIn);
} }
render() { render() {
const isLoggedIn = this.props.isLoggedIn;
return ( return (
<Card style={{ <Card style={{
...styles.card, ...styles.card,
borderColor: this.props.theme.colors.primary, borderColor: this.props.theme.colors.primary,
}}> }}>
<List.Item <List.Item
title={"AMICALE"} title={i18n.t("homeScreen.dashboard.amicaleTitle")}
description={"VOTRE COMPTE"} description={isLoggedIn
? i18n.t("homeScreen.dashboard.amicaleConnected")
: i18n.t("homeScreen.dashboard.amicaleConnect")}
left={props => <Avatar.Image left={props => <Avatar.Image
{...props} {...props}
size={40} size={40}
source={ICON_AMICALE} source={ICON_AMICALE}
style={styles.avatar}/>} style={styles.avatar}/>}
right={props => <List.Icon {...props} icon="chevron-right"/>} right={props => <List.Icon {...props} icon={isLoggedIn
onPress={() => this.props.navigation.navigate("amicale-home")} ? "chevron-right"
: "login"}/>}
onPress={isLoggedIn
? () => this.props.navigation.navigate("services", {
screen: 'index'
})
: () => this.props.navigation.navigate("login")}
style={styles.list} style={styles.list}
/> />
</Card> </Card>
@ -51,7 +63,9 @@ const styles = StyleSheet.create({
borderWidth: 1, borderWidth: 1,
}, },
avatar: { avatar: {
backgroundColor: 'transparent' backgroundColor: 'transparent',
marginTop: 'auto',
marginBottom: 'auto',
}, },
list: { list: {
// height: 50, // height: 50,

View file

@ -16,7 +16,6 @@ type Props = {
}; };
const AnimatableBadge = Animatable.createAnimatableComponent(Badge); const AnimatableBadge = Animatable.createAnimatableComponent(Badge);
const AnimatableIconButton = Animatable.createAnimatableComponent(IconButton);
/** /**
* Component used to render a small dashboard item * Component used to render a small dashboard item
@ -34,7 +33,7 @@ class SmallDashboardItem extends React.Component<Props> {
const colors = props.theme.colors; const colors = props.theme.colors;
return ( return (
<View> <View>
<AnimatableIconButton <IconButton
icon={props.icon} icon={props.icon}
color={ color={
props.isAvailable props.isAvailable

View file

@ -0,0 +1,63 @@
// @flow
import * as React from 'react';
import {Animated} from "react-native";
import ImageListItem from "./ImageListItem";
import CardListItem from "./CardListItem";
type Props = {
dataset: Array<cardItem>,
isHorizontal: boolean,
}
export type cardItem = {
title: string,
subtitle: string,
image: string | number,
onPress: () => void,
};
export type cardList = Array<cardItem>;
export default class CardList extends React.Component<Props> {
static defaultProps = {
isHorizontal: false,
}
renderItem = ({item}: { item: cardItem }) => {
if (this.props.isHorizontal)
return <ImageListItem item={item} key={item.title}/>;
else
return <CardListItem item={item} key={item.title}/>;
};
keyExtractor = (item: cardItem) => item.title;
render() {
let containerStyle;
if (this.props.isHorizontal) {
containerStyle = {
...this.props.contentContainerStyle,
height: 150,
justifyContent: 'space-around',
};
} else {
containerStyle = {
...this.props.contentContainerStyle,
}
}
return (
<Animated.FlatList
{...this.props}
data={this.props.dataset}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
numColumns={this.props.isHorizontal ? undefined : 2}
horizontal={this.props.isHorizontal}
contentContainerStyle={containerStyle}
/>
);
}
}

View file

@ -0,0 +1,44 @@
// @flow
import * as React from 'react';
import {Caption, Card, Paragraph} from 'react-native-paper';
import type {cardItem} from "./CardList";
type Props = {
item: cardItem,
}
export default class CardListItem extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
const props = this.props;
const item = props.item;
const source = typeof item.image === "number"
? item.image
: {uri: item.image};
return (
<Card
style={{
width: '40%',
margin: 5,
marginLeft: 'auto',
marginRight: 'auto',
}}
onPress={item.onPress}
>
<Card.Cover
style={{height: 80}}
source={source}
/>
<Card.Content>
<Paragraph>{item.title}</Paragraph>
<Caption>{item.subtitle}</Caption>
</Card.Content>
</Card>
);
}
}

View file

@ -0,0 +1,53 @@
// @flow
import * as React from 'react';
import {Text, TouchableRipple} from 'react-native-paper';
import {Image, View} from 'react-native';
import type {cardItem} from "./CardList";
type Props = {
item: cardItem,
}
export default class ImageListItem extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
const props = this.props;
const item = props.item;
const source = typeof item.image === "number"
? item.image
: {uri: item.image};
return (
<TouchableRipple
style={{
width: 100,
height: 150,
margin: 5,
}}
onPress={item.onPress}
>
<View>
<Image
style={{
width: 80,
height: 80,
marginLeft: 'auto',
marginRight: 'auto',
}}
source={source}
/>
<Text style={{
marginTop: 5,
marginLeft: 'auto',
marginRight: 'auto',
textAlign: 'center'
}}>{item.title}</Text>
</View>
</TouchableRipple>
);
}
}

View file

@ -6,15 +6,13 @@ import {HeaderButton, HeaderButtons} from 'react-navigation-header-buttons';
import {withTheme} from "react-native-paper"; import {withTheme} from "react-native-paper";
import * as Touchable from "react-native/Libraries/Components/Touchable/TouchableNativeFeedback.android"; import * as Touchable from "react-native/Libraries/Components/Touchable/TouchableNativeFeedback.android";
const MaterialHeaderButton = (props: Object) => ( const MaterialHeaderButton = (props: Object) => <HeaderButton
<HeaderButton
IconComponent={MaterialCommunityIcons} IconComponent={MaterialCommunityIcons}
iconSize={26} iconSize={26}
color={props.theme.colors.text} color={props.color != null ? props.color : props.theme.colors.text}
background={Touchable.Ripple(props.theme.colors.ripple, true)} background={Touchable.Ripple(props.theme.colors.ripple, true)}
{...props} {...props}
/> />;
);
const MaterialHeaderButtons = (props: Object) => { const MaterialHeaderButtons = (props: Object) => {
return ( return (

View file

@ -1,148 +0,0 @@
// @flow
import * as React from 'react';
import {FlatList} from "react-native";
import {Drawer, withTheme} from 'react-native-paper';
import {Linking} from "expo";
import AnimatedAccordion from "../Animations/AnimatedAccordion";
import {StackActions} from '@react-navigation/native';
type Props = {
navigation: Object,
startOpen: boolean,
isLoggedIn: boolean,
sectionName: string,
activeRoute: string,
listKey: string,
listData: Array<Object>,
}
const LIST_ITEM_HEIGHT = 48;
class SideBarSection extends React.PureComponent<Props> {
colors: Object;
accordionRef: {current: null | AnimatedAccordion};
constructor(props) {
super(props);
this.colors = props.theme.colors;
this.accordionRef = React.createRef();
}
/**
* Searches if the current route is contained in the given list data.
* If this is the case and the list is collapsed, we should expand this list.
*
* @return boolean
*/
shouldExpandList() {
for (let i = 0; i < this.props.listData.length; i++) {
if (this.props.listData[i].route === this.props.activeRoute) {
return true;
}
}
return false;
}
/**
* Callback when a drawer item is pressed.
* It will either navigate to the associated screen, or open the browser to the associated link
*
* @param item The item pressed
*/
onListItemPress(item: Object) {
if (item.link !== undefined)
Linking.openURL(item.link);
else if (item.action !== undefined)
item.action();
else if (this.props.activeRoute === "main")
this.props.navigation.navigate(item.route);
else {
this.props.navigation.dispatch(
StackActions.replace(item.route)
);
this.props.navigation.closeDrawer();
}
}
/**
* Key extractor for list items
*
* @param item The item to extract the key from
* @return {string} The extracted key
*/
listKeyExtractor = (item: Object) => item.route;
shouldHideItem(item: Object) {
const onlyWhenLoggedOut = item.onlyWhenLoggedOut !== undefined && item.onlyWhenLoggedOut === true;
const onlyWhenLoggedIn = item.onlyWhenLoggedIn !== undefined && item.onlyWhenLoggedIn === true;
return (onlyWhenLoggedIn && !this.props.isLoggedIn || onlyWhenLoggedOut && this.props.isLoggedIn);
}
/**
* Gets the render item for the given list item
*
* @param item The item to render
* @return {*}
*/
getRenderItem = ({item}: Object) => {
const onListItemPress = this.onListItemPress.bind(this, item);
if (this.shouldHideItem(item))
return null;
return (
<Drawer.Item
label={item.name}
active={this.props.activeRoute === item.route}
icon={item.icon}
onPress={onListItemPress}
style={{
height: LIST_ITEM_HEIGHT,
justifyContent: 'center',
}}
/>
);
};
shouldRenderAccordion() {
let itemsToRender = 0;
for (let i = 0; i < this.props.listData.length; i++) {
if (!this.shouldHideItem(this.props.listData[i]))
itemsToRender += 1;
}
return itemsToRender > 1;
}
itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
getFlatList() {
return (
// $FlowFixMe
<FlatList
data={this.props.listData}
extraData={this.props.isLoggedIn.toString() + this.props.activeRoute}
renderItem={this.getRenderItem}
keyExtractor={this.listKeyExtractor}
listKey={this.props.listKey}
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
getItemLayout={this.itemLayout}
/>
);
}
render() {
if (this.shouldRenderAccordion()) {
return (
<AnimatedAccordion
title={this.props.sectionName}
keepOpen={this.shouldExpandList()}
>
{this.getFlatList()}
</AnimatedAccordion>
);
} else
return this.getFlatList();
}
}
export default withTheme(SideBarSection);

View file

@ -1,267 +0,0 @@
// @flow
import * as React from 'react';
import {Dimensions, FlatList, Image, StyleSheet, View,} from 'react-native';
import i18n from "i18n-js";
import {TouchableRipple} from "react-native-paper";
import ConnectionManager from "../../managers/ConnectionManager";
import LogoutDialog from "../Amicale/LogoutDialog";
import SideBarSection from "./SideBarSection";
import {DrawerNavigationProp} from "@react-navigation/drawer";
const deviceWidth = Dimensions.get("window").width;
type Props = {
navigation: DrawerNavigationProp,
state: {[key: string] : any},
theme?: Object,
};
type State = {
isLoggedIn: boolean,
dialogVisible: boolean,
};
/**
* Component used to render the drawer menu content
*/
class SideBar extends React.Component<Props, State> {
dataSet: Array<Object>;
activeRoute: string;
/**
* Generate the dataset
*
* @param props
*/
constructor(props: Props) {
super(props);
this.activeRoute = 'main';
// Dataset used to render the drawer
const mainData = [
{
name: i18n.t('screens.home'),
route: "main",
icon: "home",
},
];
const amicaleData = [
{
name: i18n.t('screens.login'),
route: "login",
icon: "login",
onlyWhenLoggedOut: true,
shouldEmphasis: true,
},
{
name: i18n.t('screens.amicaleAbout'),
route: "amicale-contact",
icon: "information",
},
{
name: i18n.t('screens.profile'),
route: "profile",
icon: "account",
onlyWhenLoggedIn: true,
},
{
name: i18n.t('clubs.clubList'),
route: "club-list",
icon: "account-group",
onlyWhenLoggedIn: true,
},
{
name: i18n.t('screens.vote'),
route: "vote",
icon: "vote",
onlyWhenLoggedIn: true,
},
{
name: i18n.t('screens.logout'),
route: 'disconnect',
action: this.showDisconnectDialog,
icon: "logout",
onlyWhenLoggedIn: true,
},
];
const servicesData = [
{
name: i18n.t('screens.menuSelf'),
route: "self-menu",
icon: "silverware-fork-knife",
},
{
name: i18n.t('screens.availableRooms'),
route: "available-rooms",
icon: "calendar-check",
},
{
name: i18n.t('screens.bib'),
route: "bib",
icon: "book",
},
{
name: i18n.t('screens.bluemind'),
route: "bluemind",
link: "https://etud-mel.insa-toulouse.fr/webmail/",
icon: "email",
},
{
name: i18n.t('screens.ent'),
route: "ent",
link: "https://ent.insa-toulouse.fr/",
icon: "notebook",
},
];
const websitesData = [
{
name: "Amicale",
route: "amicale-website",
icon: "alpha-a-box",
},
{
name: "Élus Étudiants",
route: "elus-etudiants",
icon: "alpha-e-box",
},
{
name: "Wiketud",
route: "wiketud",
icon: "wikipedia",
},
{
name: "Tutor'INSA",
route: "tutorinsa",
icon: "school",
},
];
const othersData = [
{
name: i18n.t('screens.settings'),
route: "settings",
icon: "settings",
},
{
name: i18n.t('screens.about'),
route: "about",
icon: "information",
},
];
this.dataSet = [
{
key: '1',
name: i18n.t('screens.home'),
startOpen: true, // App always starts on Main
data: mainData
},
{
key: '2',
name: i18n.t('sidenav.divider4'),
startOpen: false, // TODO set by user preferences
data: amicaleData
},
{
key: '3',
name: i18n.t('sidenav.divider2'),
startOpen: false,
data: servicesData
},
{
key: '4',
name: i18n.t('sidenav.divider1'),
startOpen: false,
data: websitesData
},
{
key: '5',
name: i18n.t('sidenav.divider3'),
startOpen: false,
data: othersData
},
];
ConnectionManager.getInstance().addLoginStateListener(this.onLoginStateChange);
this.state = {
isLoggedIn: ConnectionManager.getInstance().isLoggedIn(),
dialogVisible: false,
};
}
shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
const nextNavigationState = nextProps.state.routes[0].state;
const nextRoute = nextNavigationState.routes[nextNavigationState.index].name;
let currentRoute = "main";
const currentNavigationState = this.props.state.routes[0].state;
if (currentNavigationState != null) {
currentRoute = currentNavigationState.routes[currentNavigationState.index].name;
}
this.activeRoute = nextRoute;
return (nextState !== this.state)
|| (nextRoute !== currentRoute);
}
showDisconnectDialog = () => this.setState({dialogVisible: true});
hideDisconnectDialog = () => this.setState({dialogVisible: false});
onLoginStateChange = (isLoggedIn: boolean) => this.setState({isLoggedIn: isLoggedIn});
/**
* Gets the render item for the given list item
*
* @param item The item to render
* @return {*}
*/
getRenderItem = ({item}: Object) => {
return <SideBarSection
{...this.props}
listKey={item.key}
activeRoute={this.activeRoute}
isLoggedIn={this.state.isLoggedIn}
sectionName={item.name}
startOpen={item.startOpen}
listData={item.data}
/>
};
render() {
return (
<View style={{height: '100%'}}>
<TouchableRipple
onPress={() => this.props.navigation.navigate("tetris")}
>
<Image
source={require("../../../assets/drawer-cover.png")}
style={styles.drawerCover}
/>
</TouchableRipple>
{/*$FlowFixMe*/}
<FlatList
data={this.dataSet}
extraData={this.state.isLoggedIn.toString() + this.activeRoute}
renderItem={this.getRenderItem}
/>
<LogoutDialog
{...this.props}
visible={this.state.dialogVisible}
onDismiss={this.hideDisconnectDialog}
/>
</View>
);
}
}
const styles = StyleSheet.create({
drawerCover: {
height: deviceWidth / 3,
width: 2 * deviceWidth / 3,
position: "relative",
marginBottom: 10,
marginTop: 20
},
});
export default SideBar;

View file

@ -19,8 +19,8 @@ type State = {
const TAB_ICONS = { const TAB_ICONS = {
proxiwash: 'tshirt-crew', proxiwash: 'tshirt-crew',
students: 'account-circle', services: 'account-circle',
insa: 'book', planning: 'calendar-range',
planex: 'clock', planex: 'clock',
}; };

View file

@ -7,7 +7,8 @@ import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
import DebugScreen from '../screens/About/DebugScreen'; import DebugScreen from '../screens/About/DebugScreen';
import {createStackNavigator, TransitionPresets} from "@react-navigation/stack"; import {createStackNavigator, TransitionPresets} from "@react-navigation/stack";
import i18n from "i18n-js"; import i18n from "i18n-js";
import TabNavigator from "./MainTabNavigator"; import TabNavigator from "./TabNavigator";
import TetrisScreen from "../screens/Tetris/TetrisScreen";
const defaultScreenOptions = { const defaultScreenOptions = {
gestureEnabled: true, gestureEnabled: true,
@ -59,6 +60,13 @@ function MainStackComponent(props: { createTabNavigator: () => React.Node }) {
title: i18n.t('aboutScreen.debug') title: i18n.t('aboutScreen.debug')
}} }}
/> />
<MainStack.Screen
name="tetris"
component={TetrisScreen}
options={{
title: i18n.t("game.title"),
}}
/>
</MainStack.Navigator> </MainStack.Navigator>
); );
} }
@ -68,7 +76,7 @@ type Props = {
defaultHomeData: { [key: string]: any } defaultHomeData: { [key: string]: any }
} }
export default class DrawerNavigator extends React.Component<Props> { export default class MainNavigator extends React.Component<Props> {
createTabNavigator: () => React.Node; createTabNavigator: () => React.Node;

View file

@ -21,23 +21,23 @@ import FeedItemScreen from "../screens/Home/FeedItemScreen";
import {createCollapsibleStack} from "react-navigation-collapsible"; import {createCollapsibleStack} from "react-navigation-collapsible";
import GroupSelectionScreen from "../screens/Planex/GroupSelectionScreen"; import GroupSelectionScreen from "../screens/Planex/GroupSelectionScreen";
import CustomTabBar from "../components/Tabbar/CustomTabBar"; import CustomTabBar from "../components/Tabbar/CustomTabBar";
import SelfMenuScreen from "../screens/Other/SelfMenuScreen"; import SelfMenuScreen from "../screens/Services/SelfMenuScreen";
import AvailableRoomScreen from "../screens/Websites/AvailableRoomScreen"; import AvailableRoomScreen from "../screens/Websites/AvailableRoomScreen";
import BibScreen from "../screens/Websites/BibScreen"; import BibScreen from "../screens/Websites/BibScreen";
import {AmicaleWebsiteScreen} from "../screens/Websites/AmicaleWebsiteScreen"; import {AmicaleWebsiteScreen} from "../screens/Websites/AmicaleWebsiteScreen";
import {ElusEtudiantsWebsiteScreen} from "../screens/Websites/ElusEtudiantsWebsiteScreen"; import {ElusEtudiantsWebsiteScreen} from "../screens/Websites/ElusEtudiantsWebsiteScreen";
import {WiketudWebsiteScreen} from "../screens/Websites/WiketudWebsiteScreen"; import {WiketudWebsiteScreen} from "../screens/Websites/WiketudWebsiteScreen";
import {TutorInsaWebsiteScreen} from "../screens/Websites/TutorInsaWebsiteScreen"; import {TutorInsaWebsiteScreen} from "../screens/Websites/TutorInsaWebsiteScreen";
import TetrisScreen from "../screens/Tetris/TetrisScreen"; import {ENTWebsiteScreen} from "../screens/Websites/ENTWebsiteScreen";
import {BlueMindWebsiteScreen} from "../screens/Websites/BlueMindWebsiteScreen";
import LoginScreen from "../screens/Amicale/LoginScreen"; import LoginScreen from "../screens/Amicale/LoginScreen";
import ProfileScreen from "../screens/Amicale/ProfileScreen"; import ProfileScreen from "../screens/Amicale/ProfileScreen";
import ClubListScreen from "../screens/Amicale/Clubs/ClubListScreen"; import ClubListScreen from "../screens/Amicale/Clubs/ClubListScreen";
import ClubAboutScreen from "../screens/Amicale/Clubs/ClubAboutScreen"; import ClubAboutScreen from "../screens/Amicale/Clubs/ClubAboutScreen";
import VoteScreen from "../screens/Amicale/VoteScreen"; import VoteScreen from "../screens/Amicale/VoteScreen";
import AmicaleContactScreen from "../screens/Amicale/AmicaleContactScreen"; import AmicaleContactScreen from "../screens/Amicale/AmicaleContactScreen";
import AmicaleHomeScreen from "../screens/Amicale/AmicaleHomeScreen"; import WebsitesHomeScreen from "../screens/Services/ServicesScreen";
import WebsitesHomeScreen from "../screens/Websites/WebsitesHomeScreen"; import ServicesSectionScreen from "../screens/Services/ServicesSectionScreen";
import InsaHomeScreen from "../screens/Insa/InsaHomeScreen";
const defaultScreenOptions = { const defaultScreenOptions = {
gestureEnabled: true, gestureEnabled: true,
@ -82,36 +82,34 @@ function getWebsiteStack(name: string, Stack: any, component: any, title: strin
} }
const StudentsStack = createStackNavigator(); const ServicesStack = createStackNavigator();
function StudentsStackComponent() { function ServicesStackComponent() {
return ( return (
<StudentsStack.Navigator <ServicesStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode={"screen"} headerMode={"screen"}
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<StudentsStack.Screen {createScreenCollapsibleStack("index", ServicesStack, WebsitesHomeScreen, i18n.t('screens.services'))}
name="index" {createScreenCollapsibleStack("services-section", ServicesStack, ServicesSectionScreen, "SECTION")}
component={WebsitesHomeScreen}
options={{ {/* INSA */}
title: "WEBSITES HOME", {getWebsiteStack("available-rooms", ServicesStack, AvailableRoomScreen, i18n.t('screens.availableRooms'))}
}} {getWebsiteStack("bib", ServicesStack, BibScreen, i18n.t('screens.bib'))}
/> {createScreenCollapsibleStack("self-menu", ServicesStack, SelfMenuScreen, i18n.t('screens.menuSelf'))}
{getWebsiteStack("amicale-website", StudentsStack, AmicaleWebsiteScreen, "Amicale")}
{getWebsiteStack("elus-etudiants", StudentsStack, ElusEtudiantsWebsiteScreen, "Élus Étudiants")} {/* STUDENTS */}
{getWebsiteStack("wiketud", StudentsStack, WiketudWebsiteScreen, "Wiketud")} {createScreenCollapsibleStack("proximo", ServicesStack, ProximoMainScreen, i18n.t('screens.proximo'))}
{getWebsiteStack("tutorinsa", StudentsStack, TutorInsaWebsiteScreen, "Tutor'INSA")}
{createScreenCollapsibleStack("proximo", StudentsStack, ProximoMainScreen, "Proximo")}
{createScreenCollapsibleStack( {createScreenCollapsibleStack(
"proximo-list", "proximo-list",
StudentsStack, ServicesStack,
ProximoListScreen, ProximoListScreen,
i18n.t('screens.proximoArticles'), i18n.t('screens.proximoArticles'),
true, true,
{...screenTransition}, {...screenTransition},
)} )}
<StudentsStack.Screen <ServicesStack.Screen
name="proximo-about" name="proximo-about"
component={ProximoAboutScreen} component={ProximoAboutScreen}
options={{ options={{
@ -119,22 +117,41 @@ function StudentsStackComponent() {
...modalTransition, ...modalTransition,
}} }}
/> />
<StudentsStack.Screen {getWebsiteStack("amicale-website", ServicesStack, AmicaleWebsiteScreen, i18n.t('screens.amicaleWebsite'))}
name="planning" {getWebsiteStack("elus-etudiants", ServicesStack, ElusEtudiantsWebsiteScreen, "Élus Étudiants")}
component={PlanningScreen} {getWebsiteStack("wiketud", ServicesStack, WiketudWebsiteScreen, "Wiketud")}
{getWebsiteStack("tutorinsa", ServicesStack, TutorInsaWebsiteScreen, "Tutor'INSA")}
{getWebsiteStack("ent", ServicesStack, ENTWebsiteScreen, i18n.t('screens.ent'))}
{getWebsiteStack("bluemind", ServicesStack, BlueMindWebsiteScreen, i18n.t('screens.bluemind'))}
{/* AMICALE */}
{createScreenCollapsibleStack("login", ServicesStack, LoginScreen, i18n.t('screens.login'))}
{createScreenCollapsibleStack("profile", ServicesStack, ProfileScreen, i18n.t('screens.profile'))}
{createScreenCollapsibleStack("club-list", ServicesStack, ClubListScreen, i18n.t('clubs.clubList'))}
<ServicesStack.Screen
name="club-about"
component={ClubAboutScreen}
options={{ options={{
title: i18n.t('screens.planning'), title: i18n.t('screens.clubsAbout'),
}}
/>
<StudentsStack.Screen
name="planning-information"
component={PlanningDisplayScreen}
options={{
title: i18n.t('screens.planningDisplayScreen'),
...modalTransition, ...modalTransition,
}} }}
/> />
</StudentsStack.Navigator> <ServicesStack.Screen
name="vote"
component={VoteScreen}
options={{
title: i18n.t('screens.vote'),
}}
/>
<ServicesStack.Screen
name="amicale-contact"
component={AmicaleContactScreen}
options={{
title: i18n.t('screens.amicaleAbout'),
}}
/>
</ServicesStack.Navigator>
); );
} }
@ -160,26 +177,31 @@ function ProxiwashStackComponent() {
); );
} }
const InsaStack = createStackNavigator(); const PlanningStack = createStackNavigator();
function InsaStackComponent() { function PlanningStackComponent() {
return ( return (
<InsaStack.Navigator <PlanningStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode={"screen"} headerMode={"screen"}
screenOptions={defaultScreenOptions} screenOptions={defaultScreenOptions}
> >
<InsaStack.Screen <PlanningStack.Screen
name="index" name="planning"
component={InsaHomeScreen} component={PlanningScreen}
options={{ options={{
title: "INSA HOME", title: i18n.t('screens.planning'),
}} }}
/> />
{getWebsiteStack("available-rooms", InsaStack, AvailableRoomScreen, i18n.t('screens.availableRooms'))} <PlanningStack.Screen
{getWebsiteStack("bib", InsaStack, BibScreen, i18n.t('screens.bib'))} name="planning-information"
{createScreenCollapsibleStack("self-menu", InsaStack, SelfMenuScreen, i18n.t('screens.menuSelf'))} component={PlanningDisplayScreen}
</InsaStack.Navigator> options={{
title: i18n.t('screens.planningDisplayScreen'),
...modalTransition,
}}
/>
</PlanningStack.Navigator>
); );
} }
@ -213,14 +235,6 @@ function HomeStackComponent(initialRoute: string | null, defaultData: { [key: st
useNativeDriver: true, useNativeDriver: true,
} }
)} )}
<HomeStack.Screen
name="feed-information"
component={FeedItemScreen}
options={{
title: i18n.t('screens.feedDisplayScreen'),
...modalTransition,
}}
/>
<HomeStack.Screen <HomeStack.Screen
name="scanner" name="scanner"
component={ScannerScreen} component={ScannerScreen}
@ -229,36 +243,6 @@ function HomeStackComponent(initialRoute: string | null, defaultData: { [key: st
...modalTransition, ...modalTransition,
}} }}
/> />
<HomeStack.Screen
name="home-planning-information"
component={PlanningDisplayScreen}
options={{
title: i18n.t('screens.planningDisplayScreen'),
...modalTransition,
}}
/>
<HomeStack.Screen
name="tetris"
component={TetrisScreen}
options={{
title: i18n.t("game.title"),
}}
/>
<HomeStack.Screen
name="login"
component={LoginScreen}
options={{
title: i18n.t('screens.login'),
}}
/>
<HomeStack.Screen
name="profile"
component={ProfileScreen}
options={{
title: i18n.t('screens.profile'),
}}
/>
{createScreenCollapsibleStack("club-list", HomeStack, ClubListScreen, i18n.t('clubs.clubList'))}
<HomeStack.Screen <HomeStack.Screen
name="club-information" name="club-information"
component={ClubDisplayScreen} component={ClubDisplayScreen}
@ -268,34 +252,23 @@ function HomeStackComponent(initialRoute: string | null, defaultData: { [key: st
}} }}
/> />
<HomeStack.Screen <HomeStack.Screen
name="club-about" name="feed-information"
component={ClubAboutScreen} component={FeedItemScreen}
options={{ options={{
title: i18n.t('screens.clubsAbout'), title: i18n.t('screens.feedDisplayScreen'),
...modalTransition, ...modalTransition,
}} }}
/> />
<HomeStack.Screen <HomeStack.Screen
name="vote" name="planning-information"
component={VoteScreen} component={PlanningDisplayScreen}
options={{ options={{
title: i18n.t('screens.vote'), title: i18n.t('screens.planningDisplayScreen'),
}} ...modalTransition,
/>
<HomeStack.Screen
name="amicale-contact"
component={AmicaleContactScreen}
options={{
title: i18n.t('screens.amicaleAbout'),
}}
/>
<HomeStack.Screen
name="amicale-home"
component={AmicaleHomeScreen}
options={{
title: "AMICALE HOME",
}} }}
/> />
{createScreenCollapsibleStack("self-menu", HomeStack, SelfMenuScreen, i18n.t('screens.menuSelf'), true, {...modalTransition})}
{createScreenCollapsibleStack("login", HomeStack, LoginScreen, i18n.t('screens.login'))}
</HomeStack.Navigator> </HomeStack.Navigator>
); );
} }
@ -348,27 +321,26 @@ export default class TabNavigator extends React.Component<Props> {
initialRouteName={this.defaultRoute} initialRouteName={this.defaultRoute}
tabBar={props => <CustomTabBar {...props} />} tabBar={props => <CustomTabBar {...props} />}
> >
<Tab.Screen
name="services"
option
component={ServicesStackComponent}
options={{title: i18n.t('screens.services')}}
/>
<Tab.Screen <Tab.Screen
name="proxiwash" name="proxiwash"
component={ProxiwashStackComponent} component={ProxiwashStackComponent}
options={{title: i18n.t('screens.proxiwash')}} options={{title: i18n.t('screens.proxiwash')}}
/> />
<Tab.Screen
name="students"
option
component={StudentsStackComponent}
options={{title: "ETUDIANTS"}}
/>
<Tab.Screen <Tab.Screen
name="home" name="home"
component={this.createHomeStackComponent} component={this.createHomeStackComponent}
options={{title: i18n.t('screens.home')}} options={{title: i18n.t('screens.home')}}
/> />
<Tab.Screen <Tab.Screen
name="insa" name="planning"
component={InsaStackComponent} component={PlanningStackComponent}
options={{title: "INSA"}} options={{title: i18n.t('screens.planning')}}
/> />
<Tab.Screen <Tab.Screen

View file

@ -1,16 +1,21 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {KeyboardAvoidingView, ScrollView, StyleSheet, View} from "react-native"; import {Animated, KeyboardAvoidingView, StyleSheet, View} from "react-native";
import {Avatar, Button, Card, HelperText, Paragraph, TextInput, withTheme} from 'react-native-paper'; import {Avatar, Button, Card, HelperText, Paragraph, TextInput, withTheme} from 'react-native-paper';
import ConnectionManager from "../../managers/ConnectionManager"; import ConnectionManager from "../../managers/ConnectionManager";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import ErrorDialog from "../../components/Dialogs/ErrorDialog"; import ErrorDialog from "../../components/Dialogs/ErrorDialog";
import {CommonActions} from "@react-navigation/native"; import {withCollapsible} from "../../utils/withCollapsible";
import {Collapsible} from "react-navigation-collapsible";
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
import type {CustomTheme} from "../../managers/ThemeManager";
type Props = { type Props = {
navigation: Object, navigation: Object,
route: Object, route: Object,
collapsibleStack: Collapsible,
theme: CustomTheme
} }
type State = { type State = {
@ -41,32 +46,17 @@ class LoginScreen extends React.Component<Props, State> {
dialogError: 0, dialogError: 0,
}; };
colors: Object;
onEmailChange: Function; onEmailChange: Function;
onPasswordChange: Function; onPasswordChange: Function;
passwordInputRef: Object; passwordInputRef: Object;
nextScreen: string;
constructor(props) { constructor(props) {
super(props); super(props);
this.onEmailChange = this.onInputChange.bind(this, true); this.onEmailChange = this.onInputChange.bind(this, true);
this.onPasswordChange = this.onInputChange.bind(this, false); this.onPasswordChange = this.onInputChange.bind(this, false);
this.colors = props.theme.colors;
this.props.navigation.addListener('focus', this.onScreenFocus);
} }
onScreenFocus = () => {
if (this.props.route.params !== undefined && this.props.route.params.nextScreen !== undefined) {
this.nextScreen = this.props.route.params.nextScreen;
this.props.navigation.dispatch(CommonActions.setParams({nextScreen: 'profile'}));
} else
this.nextScreen = 'profile';
};
showErrorDialog = (error: number) => showErrorDialog = (error: number) =>
this.setState({ this.setState({
dialogVisible: true, dialogVisible: true,
@ -75,7 +65,7 @@ class LoginScreen extends React.Component<Props, State> {
hideErrorDialog = () => this.setState({dialogVisible: false}); hideErrorDialog = () => this.setState({dialogVisible: false});
handleSuccess = () => this.props.navigation.replace(this.nextScreen); handleSuccess = () => this.props.navigation.goBack();
onResetPasswordClick = () => this.props.navigation.navigate('amicale-website', { onResetPasswordClick = () => this.props.navigation.navigate('amicale-website', {
screen: 'amicale-website', screen: 'amicale-website',
@ -235,7 +225,7 @@ class LoginScreen extends React.Component<Props, State> {
left={(props) => <Avatar.Icon left={(props) => <Avatar.Icon
{...props} {...props}
icon={"help"} icon={"help"}
color={this.colors.primary} color={this.props.theme.colors.primary}
style={{backgroundColor: 'transparent'}}/>} style={{backgroundColor: 'transparent'}}/>}
/> />
<Card.Content> <Card.Content>
@ -248,6 +238,7 @@ class LoginScreen extends React.Component<Props, State> {
} }
render() { render() {
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
return ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
behavior={"height"} behavior={"height"}
@ -256,7 +247,14 @@ class LoginScreen extends React.Component<Props, State> {
enabled enabled
keyboardVerticalOffset={100} keyboardVerticalOffset={100}
> >
<ScrollView> <Animated.ScrollView
onScroll={onScroll}
contentContainerStyle={{
paddingTop: containerPaddingTop,
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20
}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
>
<View> <View>
{this.getMainCard()} {this.getMainCard()}
{this.getSecondaryCard()} {this.getSecondaryCard()}
@ -266,7 +264,7 @@ class LoginScreen extends React.Component<Props, State> {
onDismiss={this.hideErrorDialog} onDismiss={this.hideErrorDialog}
errorCode={this.state.dialogError} errorCode={this.state.dialogError}
/> />
</ScrollView> </Animated.ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
); );
} }
@ -292,4 +290,4 @@ const styles = StyleSheet.create({
} }
}); });
export default withTheme(LoginScreen); export default withCollapsible(withTheme(LoginScreen));

View file

@ -1,16 +1,20 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {FlatList, StyleSheet, View} from "react-native"; import {Animated, FlatList, StyleSheet, View} from "react-native";
import {Avatar, Button, Card, Divider, List, withTheme} from 'react-native-paper'; import {Avatar, Button, Card, Divider, List, withTheme} from 'react-native-paper';
import AuthenticatedScreen from "../../components/Amicale/AuthenticatedScreen"; import AuthenticatedScreen from "../../components/Amicale/AuthenticatedScreen";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import LogoutDialog from "../../components/Amicale/LogoutDialog"; import LogoutDialog from "../../components/Amicale/LogoutDialog";
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton"; import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
import {Collapsible} from "react-navigation-collapsible";
import {withCollapsible} from "../../utils/withCollapsible";
type Props = { type Props = {
navigation: Object, navigation: Object,
theme: Object, theme: Object,
collapsibleStack: Collapsible,
} }
type State = { type State = {
@ -52,12 +56,20 @@ class ProfileScreen extends React.Component<Props, State> {
getScreen = (data: Object) => { getScreen = (data: Object) => {
this.data = data[0]; this.data = data[0];
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
return ( return (
<View> <View style={{flex: 1}}>
{/*$FlowFixMe*/} <Animated.FlatList
<FlatList
renderItem={this.getRenderItem} renderItem={this.getRenderItem}
data={this.flatListData} data={this.flatListData}
// Animations
onScroll={onScroll}
contentContainerStyle={{
paddingTop: containerPaddingTop,
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT,
minHeight: '100%'
}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
/> />
<LogoutDialog <LogoutDialog
{...this.props} {...this.props}
@ -323,4 +335,4 @@ const styles = StyleSheet.create({
}); });
export default withTheme(ProfileScreen); export default withCollapsible(withTheme(ProfileScreen));

View file

@ -17,6 +17,9 @@ import AnimatedFAB from "../../components/Animations/AnimatedFAB";
import {StackNavigationProp} from "@react-navigation/stack"; import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../managers/ThemeManager"; import type {CustomTheme} from "../../managers/ThemeManager";
import {View} from "react-native-animatable"; import {View} from "react-native-animatable";
import {HiddenItem} from "react-navigation-header-buttons";
import ConnectionManager from "../../managers/ConnectionManager";
import LogoutDialog from "../../components/Amicale/LogoutDialog";
// import DATA from "../dashboard_data.json"; // import DATA from "../dashboard_data.json";
@ -95,21 +98,32 @@ type Props = {
theme: CustomTheme, theme: CustomTheme,
} }
type State = {
dialogVisible: boolean,
}
/** /**
* Class defining the app's home screen * Class defining the app's home screen
*/ */
class HomeScreen extends React.Component<Props> { class HomeScreen extends React.Component<Props, State> {
colors: Object; colors: Object;
isLoggedIn: boolean | null;
fabRef: { current: null | AnimatedFAB }; fabRef: { current: null | AnimatedFAB };
currentNewFeed: Array<feedItem>; currentNewFeed: Array<feedItem>;
state = {
dialogVisible: false,
}
constructor(props) { constructor(props) {
super(props); super(props);
this.colors = props.theme.colors; this.colors = props.theme.colors;
this.fabRef = React.createRef(); this.fabRef = React.createRef();
this.currentNewFeed = []; this.currentNewFeed = [];
this.isLoggedIn = null;
} }
/** /**
@ -130,9 +144,12 @@ class HomeScreen extends React.Component<Props> {
} }
onScreenFocus = () => { onScreenFocus = () => {
if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
this.props.navigation.setOptions({ this.props.navigation.setOptions({
headerRight: this.getHeaderButton, headerRight: this.getHeaderButton,
}); });
}
// handle link open when home is not focused or created // handle link open when home is not focused or created
this.handleNavigationParams(); this.handleNavigationParams();
}; };
@ -148,25 +165,43 @@ class HomeScreen extends React.Component<Props> {
}; };
getHeaderButton = () => { getHeaderButton = () => {
let onPressLog = () => this.props.navigation.navigate("login");
let logIcon = "login";
let logColor = this.props.theme.colors.primary;
if (this.isLoggedIn) {
onPressLog = () => this.showDisconnectDialog();
logIcon = "logout";
logColor = this.props.theme.colors.text;
}
const onPressSettings = () => this.props.navigation.navigate("settings"); const onPressSettings = () => this.props.navigation.navigate("settings");
const onPressAbout = () => this.props.navigation.navigate("about"); const onPressAbout = () => this.props.navigation.navigate("about");
return <MaterialHeaderButtons> return <MaterialHeaderButtons>
<Item title="settings" iconName={"settings"} onPress={onPressSettings}/> <Item title="log" iconName={logIcon} color={logColor} onPress={onPressLog}/>
<Item title="information" iconName={"information"} onPress={onPressAbout}/> <HiddenItem title={i18n.t("screens.settings")} iconName={"settings"} onPress={onPressSettings}/>
<HiddenItem title={i18n.t("screens.about")} iconName={"information"} onPress={onPressAbout}/>
</MaterialHeaderButtons>; </MaterialHeaderButtons>;
}; };
showDisconnectDialog = () => this.setState({dialogVisible: true});
hideDisconnectDialog = () => this.setState({dialogVisible: false});
onProxiwashClick = () => { onProxiwashClick = () => {
this.props.navigation.navigate("proxiwash"); this.props.navigation.navigate("proxiwash");
}; };
onProximoClick = () => { onProximoClick = () => {
this.props.navigation.navigate("proximo"); this.props.navigation.navigate('services', {screen: "index"});
}; };
onTutorInsaClick = () => this.props.navigation.navigate('tutorinsa'); onTutorInsaClick = () => {
this.props.navigation.navigate('services', {screen: "index"});
};
onMenuClick = () => this.props.navigation.navigate('self-menu'); onMenuClick = () => {
this.props.navigation.navigate('self-menu');
};
/** /**
* Creates the dataset to be used in the FlatList * Creates the dataset to be used in the FlatList
@ -244,7 +279,7 @@ class HomeScreen extends React.Component<Props> {
}, },
{ {
id: 'today_menu', id: 'today_menu',
data: dashboardData == null ? 0 : dashboardData.today_menu, data: dashboardData == null ? [] : dashboardData.today_menu,
icon: 'silverware-fork-knife', icon: 'silverware-fork-knife',
color: this.colors.menuColor, color: this.colors.menuColor,
onPress: this.onMenuClick, onPress: this.onMenuClick,
@ -277,7 +312,7 @@ class HomeScreen extends React.Component<Props> {
} }
getDashboardActions() { getDashboardActions() {
return <ActionsDashBoardItem {...this.props}/>; return <ActionsDashBoardItem {...this.props} isLoggedIn={this.isLoggedIn}/>;
} }
/** /**
@ -407,8 +442,11 @@ class HomeScreen extends React.Component<Props> {
getDashboardEvent(content: Array<event>) { getDashboardEvent(content: Array<event>) {
let futureEvents = this.getFutureEvents(content); let futureEvents = this.getFutureEvents(content);
let displayEvent = this.getDisplayEvent(futureEvents); let displayEvent = this.getDisplayEvent(futureEvents);
const clickPreviewAction = () => // const clickPreviewAction = () =>
this.props.navigation.navigate('home-planning-information', {data: displayEvent}); // this.props.navigation.navigate('students', {
// screen: 'planning-information',
// params: {data: displayEvent}
// });
return ( return (
<DashboardItem <DashboardItem
eventNumber={futureEvents.length} eventNumber={futureEvents.length}
@ -416,7 +454,7 @@ class HomeScreen extends React.Component<Props> {
> >
<PreviewEventDashboardItem <PreviewEventDashboardItem
event={displayEvent != null ? displayEvent : undefined} event={displayEvent != null ? displayEvent : undefined}
clickAction={clickPreviewAction} clickAction={this.onEventContainerClick}
/> />
</DashboardItem> </DashboardItem>
); );
@ -522,6 +560,11 @@ class HomeScreen extends React.Component<Props> {
icon="qrcode-scan" icon="qrcode-scan"
onPress={this.openScanner} onPress={this.openScanner}
/> />
<LogoutDialog
{...this.props}
visible={this.state.dialogVisible}
onDismiss={this.hideDisconnectDialog}
/>
</View> </View>
); );
} }

View file

@ -1,85 +0,0 @@
// @flow
import * as React from 'react';
import {ScrollView, StyleSheet} from "react-native";
import {Button, withTheme} from 'react-native-paper';
type Props = {
navigation: Object,
route: Object,
}
type State = {}
class InsaHomeScreen extends React.Component<Props, State> {
state = {};
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
render() {
const nav = this.props.navigation;
return (
<ScrollView>
<Button
icon={"information"}
onPress={() => nav.navigate("self-menu")}
>
RU
</Button>
<Button
icon={"information"}
onPress={() => nav.navigate("available-rooms")}
>
AVAILABLE ROOMS
</Button>
<Button
icon={"information"}
onPress={() => nav.navigate("bib")}
>
BIB
</Button>
<Button// TODO create webview
icon={"information"}
onPress={() => nav.navigate("self-menu")}
>
EMAIL
</Button>
<Button// TODO create webview
icon={"information"}
onPress={() => nav.navigate("self-menu")}
>
ENT
</Button>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
card: {
margin: 10,
},
header: {
fontSize: 36,
marginBottom: 48
},
textInput: {},
btnContainer: {
marginTop: 5,
marginBottom: 10,
}
});
export default withTheme(InsaHomeScreen);

View file

@ -10,6 +10,8 @@ type Props = {
navigation: Object, navigation: Object,
}; };
const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/proximo-logo.png";
/** /**
* Class defining the proximo about screen. * Class defining the proximo about screen.
*/ */
@ -27,9 +29,8 @@ export default class ProximoAboutScreen extends React.Component<Props> {
alignItems: 'center' alignItems: 'center'
}}> }}>
<Image <Image
source={require('../../../assets/proximo-logo.png')} source={{uri: LOGO}}
style={{flex: 1, resizeMode: "contain"}} style={{height: '100%', width: '100%', resizeMode: "contain"}}/>
resizeMode="contain"/>
</View> </View>
<Text>{i18n.t('proximoScreen.description')}</Text> <Text>{i18n.t('proximoScreen.description')}</Text>
<Card style={{margin: 5}}> <Card style={{margin: 5}}>

View file

@ -10,6 +10,8 @@ type Props = {
navigation: Object, navigation: Object,
}; };
const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/proxiwash-logo.png";
/** /**
* Class defining the proxiwash about screen. * Class defining the proxiwash about screen.
*/ */
@ -27,9 +29,8 @@ export default class ProxiwashAboutScreen extends React.Component<Props> {
alignItems: 'center' alignItems: 'center'
}}> }}>
<Image <Image
source={require('../../../assets/proxiwash-logo.png')} source={{uri: LOGO}}
style={{flex: 1, resizeMode: "contain"}} style={{height: '100%', width: '100%', resizeMode: "contain"}}/>
resizeMode="contain"/>
</View> </View>
<Text>{i18n.t('proxiwashScreen.description')}</Text> <Text>{i18n.t('proxiwashScreen.description')}</Text>
<Card style={{margin: 5}}> <Card style={{margin: 5}}>

View file

@ -0,0 +1,291 @@
// @flow
import * as React from 'react';
import type {cardList} from "../../components/Lists/CardList/CardList";
import CardList from "../../components/Lists/CardList/CardList";
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
import {withCollapsible} from "../../utils/withCollapsible";
import {Collapsible} from "react-navigation-collapsible";
import {CommonActions} from "@react-navigation/native";
import {Animated, View} from "react-native";
import {Avatar, Button, Card, Divider, List, Title, TouchableRipple, withTheme} from "react-native-paper";
import type {CustomTheme} from "../../managers/ThemeManager";
import ConnectionManager from "../../managers/ConnectionManager";
import i18n from 'i18n-js';
type Props = {
navigation: Object,
route: Object,
collapsibleStack: Collapsible,
theme: CustomTheme,
}
const BIB_IMAGE = "https://scontent-cdg2-1.xx.fbcdn.net/v/t1.0-9/50695561_2124263197597162_2325349608210825216_n.jpg?_nc_cat=109&_nc_sid=8bfeb9&_nc_ohc=tmcV6FWO7_kAX9vfWHU&_nc_ht=scontent-cdg2-1.xx&oh=3b81c76e46b49f7c3a033ea3b07ec212&oe=5EC59B4D";
const RU_IMAGE = "https://scontent-cdg2-1.xx.fbcdn.net/v/t1.0-9/47123773_2041883702501779_5289372776166064128_o.jpg?_nc_cat=100&_nc_sid=cdbe9c&_nc_ohc=dpuBGlIIy_EAX8CyC0l&_nc_ht=scontent-cdg2-1.xx&oh=5c5bb4f0c7f12b554246f7c9b620a5f3&oe=5EC4DB31";
const ROOM_IMAGE = "https://scontent-cdt1-1.xx.fbcdn.net/v/t1.0-9/47041013_2043521689004647_316124496522117120_n.jpg?_nc_cat=103&_nc_sid=8bfeb9&_nc_ohc=bIp8OVJvvSEAX8mKnDZ&_nc_ht=scontent-cdt1-1.xx&oh=b4fef72a645804a849ad30e9e20fca12&oe=5EC29309";
const EMAIL_IMAGE = "https://etud-mel.insa-toulouse.fr/webmail/images/logo-bluemind.png";
const ENT_IMAGE = "https://ent.insa-toulouse.fr/media/org/jasig/portal/layout/tab-column/xhtml-theme/insa/institutional/LogoInsa.png";
const PROXIMO_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/proximo-logo.png"
const WIKETUD_LINK = "https://wiki.etud.insa-toulouse.fr/resources/assets/wiketud.png?ff051";
const AMICALE_IMAGE = require("../../../assets/amicale.png");
const EE_IMAGE = "https://etud.insa-toulouse.fr/~eeinsat/wp-content/uploads/2019/09/logo-blanc.png";
const TUTORINSA_IMAGE = "https://www.etud.insa-toulouse.fr/~tutorinsa/public/images/logo-gray.png";
export type listItem = {
title: string,
description: string,
image: string | number,
shouldLogin: boolean,
content: cardList,
}
type State = {
isLoggedIn: boolean,
}
class ServicesScreen extends React.Component<Props, State> {
amicaleDataset: cardList;
studentsDataset: cardList;
insaDataset: cardList;
finalDataset: Array<listItem>
constructor(props) {
super(props);
const nav = props.navigation;
this.amicaleDataset = [
{
title: i18n.t('screens.clubsAbout'),
subtitle: "CLUB LIST",
image: AMICALE_IMAGE,
onPress: () => nav.navigate("club-list"),
},
{
title: i18n.t('screens.profile'),
subtitle: "PROFIL",
image: AMICALE_IMAGE,
onPress: () => nav.navigate("profile"),
},
{
title: i18n.t('screens.amicaleAbout'),
subtitle: "CONTACT",
image: AMICALE_IMAGE,
onPress: () => nav.navigate("amicale-contact"),
},
{
title: i18n.t('screens.vote'),
subtitle: "ELECTIONS",
image: AMICALE_IMAGE,
onPress: () => nav.navigate("vote"),
},
];
this.studentsDataset = [
{
title: i18n.t('screens.proximo'),
subtitle: "proximo",
image: PROXIMO_IMAGE,
onPress: () => nav.navigate("proximo"),
},
{
title: i18n.t('screens.amicaleWebsite'),
subtitle: "AMICALE",
image: AMICALE_IMAGE,
onPress: () => nav.navigate("amicale-website"),
},
{
title: "Wiketud",
subtitle: "wiketud",
image: WIKETUD_LINK,
onPress: () => nav.navigate("wiketud"),
},
{
title: "Élus Étudiants",
subtitle: "ELUS ETUDIANTS",
image: EE_IMAGE,
onPress: () => nav.navigate("elus-etudiants"),
},
{
title: "Tutor'INSA",
subtitle: "TUTOR INSA",
image: TUTORINSA_IMAGE,
onPress: () => nav.navigate("tutorinsa"),
},
];
this.insaDataset = [
{
title: i18n.t('screens.menuSelf'),
subtitle: "the ru",
image: RU_IMAGE,
onPress: () => nav.navigate("self-menu"),
},
{
title: i18n.t('screens.availableRooms'),
subtitle: "ROOMS",
image: ROOM_IMAGE,
onPress: () => nav.navigate("available-rooms"),
},
{
title: i18n.t('screens.bib'),
subtitle: "BIB",
image: BIB_IMAGE,
onPress: () => nav.navigate("bib"),
},
{
title: i18n.t('screens.bluemind'),
subtitle: "EMAIL",
image: EMAIL_IMAGE,
onPress: () => nav.navigate("bluemind"),
},
{
title: i18n.t('screens.ent'),
subtitle: "ENT",
image: ENT_IMAGE,
onPress: () => nav.navigate("ent"),
},
];
this.finalDataset = [
{
title: i18n.t("servicesScreen.amicale"),
description: "LOGIN",
image: AMICALE_IMAGE,
shouldLogin: true,
content: this.amicaleDataset
},
{
title: i18n.t("servicesScreen.students"),
description: "SERVICES OFFERED BY STUDENTS",
image: 'account-group',
shouldLogin: false,
content: this.studentsDataset
},
{
title: i18n.t("servicesScreen.insa"),
description: "SERVICES OFFERED BY INSA",
image: 'school',
shouldLogin: false,
content: this.insaDataset
},
];
this.state = {
isLoggedIn: ConnectionManager.getInstance().isLoggedIn()
}
}
componentDidMount() {
this.props.navigation.addListener('focus', this.onFocus);
}
onFocus = () => {
this.handleNavigationParams();
this.setState({isLoggedIn: ConnectionManager.getInstance().isLoggedIn()})
}
handleNavigationParams() {
if (this.props.route.params != null) {
if (this.props.route.params.nextScreen != null) {
this.props.navigation.navigate(this.props.route.params.nextScreen);
// reset params to prevent infinite loop
this.props.navigation.dispatch(CommonActions.setParams({nextScreen: null}));
}
}
};
getAvatar(props, source: string | number) {
if (typeof source === "number")
return <Avatar.Image
{...props}
size={48}
source={AMICALE_IMAGE}
style={{backgroundColor: 'transparent'}}
/>
else
return <Avatar.Icon
{...props}
size={48}
icon={source}
color={this.props.theme.colors.primary}
style={{backgroundColor: 'transparent'}}
/>
}
getLoginMessage() {
return (
<View>
<Title style={{
marginLeft: 'auto',
marginRight: 'auto',
}}>
{i18n.t("servicesScreen.notLoggedIn")}
</Title>
<Button
icon="login"
mode="contained"
onPress={() => this.props.navigation.navigate("login")}
style={{
marginLeft: 'auto',
marginRight: 'auto',
}}>
{i18n.t("screens.login")}
</Button>
</View>
)
}
renderItem = ({item}: { item: listItem }) => {
const shouldShowLogin = !this.state.isLoggedIn && item.shouldLogin;
return (
<TouchableRipple
style={{
margin: 5,
marginBottom: 20,
}}
onPress={shouldShowLogin
? undefined
: () => this.props.navigation.navigate("services-section", {data: item})}
>
<View>
<Card.Title
title={item.title}
left={(props) => this.getAvatar(props, item.image)}
right={shouldShowLogin
? undefined
: (props) => <List.Icon {...props} icon="chevron-right"/>}
/>
{
shouldShowLogin
? this.getLoginMessage()
: <CardList
dataset={item.content}
isHorizontal={true}
/>
}
</View>
</TouchableRipple>
);
};
keyExtractor = (item: listItem) => {
return item.title;
}
render() {
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
return <Animated.FlatList
data={this.finalDataset}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
onScroll={onScroll}
contentContainerStyle={{
paddingTop: containerPaddingTop,
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20
}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
ItemSeparatorComponent={() => <Divider/>}
/>
}
}
export default withCollapsible(withTheme(ServicesScreen));

View file

@ -0,0 +1,76 @@
// @flow
import * as React from 'react';
import CardList from "../../components/Lists/CardList/CardList";
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
import {withCollapsible} from "../../utils/withCollapsible";
import {Collapsible} from "react-navigation-collapsible";
import {CommonActions} from "@react-navigation/native";
import ConnectionManager from "../../managers/ConnectionManager";
import type {listItem} from "./ServicesScreen";
import ErrorView from "../../components/Screens/ErrorView";
import {ERROR_TYPE} from "../../utils/WebData";
type Props = {
navigation: Object,
route: Object,
collapsibleStack: Collapsible,
}
type State = {
isLoggedIn: boolean,
}
class ServicesSectionScreen extends React.Component<Props, State> {
finalDataset: listItem;
constructor(props) {
super(props);
this.handleNavigationParams();
this.state = {
isLoggedIn: ConnectionManager.getInstance().isLoggedIn(),
}
}
componentDidMount() {
this.props.navigation.addListener('focus', this.onFocus);
}
onFocus = () => {
this.setState({isLoggedIn: ConnectionManager.getInstance().isLoggedIn()})
}
handleNavigationParams() {
if (this.props.route.params != null) {
if (this.props.route.params.data != null) {
this.finalDataset = this.props.route.params.data;
// reset params to prevent infinite loop
this.props.navigation.dispatch(CommonActions.setParams({data: null}));
this.props.navigation.setOptions({
headerTitle: this.finalDataset.title,
});
}
}
}
render() {
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
if (!this.state.isLoggedIn && this.finalDataset.shouldLogin)
return <ErrorView {...this.props} errorCode={ERROR_TYPE.BAD_TOKEN}/>;
else
return <CardList
dataset={this.finalDataset.content}
isHorizontal={false}
onScroll={onScroll}
contentContainerStyle={{
paddingTop: containerPaddingTop,
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20
}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
/>
}
}
export default withCollapsible(ServicesSectionScreen);

View file

@ -0,0 +1,18 @@
// @flow
import * as React from 'react';
import WebViewScreen from "../../components/Screens/WebViewScreen";
const URL = 'https://etud-mel.insa-toulouse.fr/webmail/';
/**
* Class defining the app's available rooms screen.
* This screen uses a webview to render the page
*/
export const BlueMindWebsiteScreen = (props: Object) => {
return (
<WebViewScreen
{...props}
url={URL}/>
);
};

View file

@ -0,0 +1,18 @@
// @flow
import * as React from 'react';
import WebViewScreen from "../../components/Screens/WebViewScreen";
const URL = 'https://ent.insa-toulouse.fr/';
/**
* Class defining the app's available rooms screen.
* This screen uses a webview to render the page
*/
export const ENTWebsiteScreen = (props: Object) => {
return (
<WebViewScreen
{...props}
url={URL}/>
);
};

View file

@ -1,91 +0,0 @@
// @flow
import * as React from 'react';
import {ScrollView, StyleSheet} from "react-native";
import {Button, withTheme} from 'react-native-paper';
type Props = {
navigation: Object,
route: Object,
}
type State = {}
class WebsitesHomeScreen extends React.Component<Props, State> {
state = {};
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
render() {
const nav = this.props.navigation;
return (
<ScrollView>
<Button
icon={"information"}
onPress={() => nav.navigate("amicale-website")}
>
AMICALE
</Button>
<Button
icon={"information"}
onPress={() => nav.navigate("elus-etudiants")}
>
ELUS ETUDIANTS
</Button>
<Button
icon={"information"}
onPress={() => nav.navigate("tutorinsa")}
>
TUTOR INSA
</Button>
<Button
icon={"information"}
onPress={() => nav.navigate("wiketud")}
>
WIKETUD
</Button>
<Button
icon={"information"}
onPress={() => nav.navigate("proximo")}
>
PROXIMO
</Button>
<Button
icon={"information"}
onPress={() => nav.navigate("planning")}
>
PLANNING
</Button>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
card: {
margin: 10,
},
header: {
fontSize: 36,
marginBottom: 48
},
textInput: {},
btnContainer: {
marginTop: 5,
marginBottom: 10,
}
});
export default withTheme(WebsitesHomeScreen);

View file

@ -1,13 +1,15 @@
{ {
"screens": { "screens": {
"home": "Home", "home": "Home",
"planning": "Planning", "planning": "Events",
"planningDisplayScreen": "Event details", "planningDisplayScreen": "Event details",
"clubDisplayScreen": "Club details", "clubDisplayScreen": "Club details",
"feedDisplayScreen": "Details", "feedDisplayScreen": "Details",
"clubsAbout": "Clubs", "clubsAbout": "Clubs",
"amicaleAbout": "The Amicale", "amicaleAbout": "Contact",
"amicaleWebsite": "Amicale's website",
"proxiwash": "Proxiwash", "proxiwash": "Proxiwash",
"services": "Services",
"proximo": "Proximo", "proximo": "Proximo",
"proximoArticles": "Articles", "proximoArticles": "Articles",
"menuSelf": "RU Menu", "menuSelf": "RU Menu",
@ -95,28 +97,11 @@
"todayEventsSubtitleNA": "No events today", "todayEventsSubtitleNA": "No events today",
"todayEventsSubtitle": " event coming today", "todayEventsSubtitle": " event coming today",
"todayEventsSubtitlePlural": " events coming today", "todayEventsSubtitlePlural": " events coming today",
"proximoTitle": "Proximo", "amicaleTitle": "The Amicale",
"proximoSubtitleNA": "No articles available", "amicaleConnect": "Login",
"proximoSubtitle": " article available", "amicaleConnected": "See available services"
"proximoSubtitlePlural": " articles available",
"tutorinsaSubtitleNA": "No tutorial available",
"tutorinsaSubtitle": " tutorial available",
"tutorinsaSubtitlePlural": " tutorials available",
"proxiwashTitle": "Available machines",
"proxiwashSubtitleNA": "No machines available",
"proxiwashSubtitle1": " dryer",
"proxiwashSubtitle1Plural": " dryers",
"proxiwashSubtitle2": " washer",
"proxiwashSubtitle2Plural": " washers",
"menuTitle": "Today's menu",
"menuSubtitleNA": "No menu available",
"menuSubtitle": "Click here to see the menu"
} }
}, },
"planningScreen": {
"wipTitle": "WORK IN PROGRESS",
"wipSubtitle": "Soon, every event at the INSA Toulouse in one place !"
},
"aboutScreen": { "aboutScreen": {
"appstore": "See on the Appstore", "appstore": "See on the Appstore",
"playstore": "See on the Playstore", "playstore": "See on the Playstore",
@ -397,5 +382,11 @@
"time": "Time: ", "time": "Time: ",
"exit": "leave Game" "exit": "leave Game"
} }
},
"servicesScreen": {
"amicale": "The Amicale",
"students": "Student services",
"insa": "INSA services",
"notLoggedIn": "Not logged in"
} }
} }

View file

@ -1,13 +1,15 @@
{ {
"screens": { "screens": {
"home": "Accueil", "home": "Accueil",
"planning": "Planning", "planning": "Événements",
"planningDisplayScreen": "Détails", "planningDisplayScreen": "Détails",
"clubDisplayScreen": "Détails", "clubDisplayScreen": "Détails",
"feedDisplayScreen": "Détails", "feedDisplayScreen": "Détails",
"clubsAbout": "Les Clubs", "clubsAbout": "Les Clubs",
"amicaleAbout": "L' Amicale", "amicaleAbout": "Contact",
"amicaleWebsite": "Site de l'Amicale",
"proxiwash": "Proxiwash", "proxiwash": "Proxiwash",
"services": "Services",
"proximo": "Proximo", "proximo": "Proximo",
"proximoArticles": "Articles", "proximoArticles": "Articles",
"menuSelf": "Menu du RU", "menuSelf": "Menu du RU",
@ -95,28 +97,11 @@
"todayEventsSubtitleNA": "Pas d'événement", "todayEventsSubtitleNA": "Pas d'événement",
"todayEventsSubtitle": " événement aujourd'hui", "todayEventsSubtitle": " événement aujourd'hui",
"todayEventsSubtitlePlural": " événements aujourd'hui", "todayEventsSubtitlePlural": " événements aujourd'hui",
"proximoTitle": "Proximo", "amicaleTitle": "L'Amicale",
"proximoSubtitleNA": "pas d'article en vente", "amicaleConnect": "Se connecter",
"proximoSubtitle": " article disponible", "amicaleConnected": "Voir les services disponibles"
"proximoSubtitlePlural": " articles disponibles",
"tutorinsaSubtitleNA": "Aucun tutorat disponible",
"tutorinsaSubtitle": " tutorat disponible",
"tutorinsaSubtitlePlural": " tutorats disponibles",
"proxiwashTitle": "Machines disponibles",
"proxiwashSubtitleNA": "Pas de machine disponible",
"proxiwashSubtitle1": " sèche-linge",
"proxiwashSubtitle1Plural": " sèche-linges",
"proxiwashSubtitle2": " lave-linge",
"proxiwashSubtitle2Plural": " lave-linges",
"menuTitle": "Menu d'aujourd'hui",
"menuSubtitleNA": "Pas de menu disponible",
"menuSubtitle": "Cliquez ici pour voir le menu"
} }
}, },
"planningScreen": {
"wipTitle": "WORK IN PROGRESS",
"wipSubtitle": "Bientôt, tous les évènements de l'INSA Toulouse en un seul endroit !"
},
"aboutScreen": { "aboutScreen": {
"appstore": "Voir sur l'Appstore", "appstore": "Voir sur l'Appstore",
"playstore": "Voir sur le Playstore", "playstore": "Voir sur le Playstore",
@ -397,5 +382,11 @@
"time": "Temps: ", "time": "Temps: ",
"exit": "Quitter" "exit": "Quitter"
} }
},
"servicesScreen": {
"amicale": "L'Amicale",
"students": "Services étudiants",
"insa": "Services de l'INSA",
"notLoggedIn": "Non connecté"
} }
} }