Compare commits
16 commits
4c4f968e25
...
7162978503
| Author | SHA1 | Date | |
|---|---|---|---|
| 7162978503 | |||
| b7e1d56ff6 | |||
| f617dba1e6 | |||
| 0267ff70ce | |||
| 2fc2db39c7 | |||
| f8363353c3 | |||
| 0410499593 | |||
| 43a3d64deb | |||
| c4337b13cb | |||
| b7d6c98025 | |||
| 033bb388fd | |||
| 56effeaaf9 | |||
| b83e748d29 | |||
| 65ba27ea26 | |||
| 3a3cf200f5 | |||
| 3bac6d6662 |
19 changed files with 411 additions and 239 deletions
22
App.js
22
App.js
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Platform, StatusBar, YellowBox} from 'react-native';
|
||||
import {Platform, StatusBar, View, YellowBox} from 'react-native';
|
||||
import LocaleManager from './src/managers/LocaleManager';
|
||||
import AsyncStorageManager from "./src/managers/AsyncStorageManager";
|
||||
import CustomIntroSlider from "./src/components/Overrides/CustomIntroSlider";
|
||||
|
|
@ -20,7 +20,7 @@ import {setSafeBounceHeight} from "react-navigation-collapsible";
|
|||
import {enableScreens} from 'react-native-screens';
|
||||
|
||||
// Native optimizations https://reactnavigation.org/docs/react-native-screens
|
||||
enableScreens();
|
||||
enableScreens(true);
|
||||
|
||||
|
||||
YellowBox.ignoreWarnings([ // collapsible headers cause this warning, just ignore as it is not an issue
|
||||
|
|
@ -71,6 +71,7 @@ export default class App extends React.Component<Props, State> {
|
|||
this.loadAssetsAsync().then(() => {
|
||||
this.onLoadFinished();
|
||||
});
|
||||
// console.log(Linking.makeUrl('path/into/app', { hello: 'world', goodbye: 'now' }))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -120,7 +121,8 @@ export default class App extends React.Component<Props, State> {
|
|||
} else {
|
||||
StatusBar.setBarStyle('dark-content', true);
|
||||
}
|
||||
StatusBar.setBackgroundColor(ThemeManager.getCurrentTheme().colors.surface, true);
|
||||
if (Platform.OS === "android")
|
||||
StatusBar.setBackgroundColor(ThemeManager.getCurrentTheme().colors.surface, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -190,12 +192,14 @@ export default class App extends React.Component<Props, State> {
|
|||
} else {
|
||||
return (
|
||||
<PaperProvider theme={this.state.currentTheme}>
|
||||
<NavigationContainer theme={this.state.currentTheme} ref={this.navigatorRef}>
|
||||
<MainNavigator
|
||||
defaultHomeRoute={this.defaultHomeRoute}
|
||||
defaultHomeData={this.defaultHomeData}
|
||||
/>
|
||||
</NavigationContainer>
|
||||
<View style={{backgroundColor: ThemeManager.getCurrentTheme().colors.background, flex: 1}}>
|
||||
<NavigationContainer theme={this.state.currentTheme} ref={this.navigatorRef}>
|
||||
<MainNavigator
|
||||
defaultHomeRoute={this.defaultHomeRoute}
|
||||
defaultHomeData={this.defaultHomeData}
|
||||
/>
|
||||
</NavigationContainer>
|
||||
</View>
|
||||
</PaperProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,12 @@ type Props = {
|
|||
mandatory: boolean
|
||||
}>,
|
||||
renderFunction: (Array<{ [key: string]: any } | null>) => React.Node,
|
||||
errorViewOverride?: Array<{
|
||||
errorCode: number,
|
||||
message: string,
|
||||
icon: string,
|
||||
showRetryButton: boolean
|
||||
}>,
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
|
@ -158,13 +164,38 @@ class AuthenticatedScreen extends React.Component<Props, State> {
|
|||
* @return {*}
|
||||
*/
|
||||
getErrorRender() {
|
||||
return (
|
||||
<ErrorView
|
||||
{...this.props}
|
||||
errorCode={this.getError()}
|
||||
onRefresh={this.fetchData}
|
||||
/>
|
||||
);
|
||||
const errorCode = this.getError();
|
||||
let shouldOverride = false;
|
||||
let override = null;
|
||||
const overrideList = this.props.errorViewOverride;
|
||||
if (overrideList != null) {
|
||||
for (let i = 0; i < overrideList.length; i++) {
|
||||
if (overrideList[i].errorCode === errorCode) {
|
||||
shouldOverride = true;
|
||||
override = overrideList[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldOverride && override != null) {
|
||||
return (
|
||||
<ErrorView
|
||||
{...this.props}
|
||||
icon={override.icon}
|
||||
message={override.message}
|
||||
showRetryButton={override.showRetryButton}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ErrorView
|
||||
{...this.props}
|
||||
errorCode={errorCode}
|
||||
onRefresh={this.fetchData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ type Props = {
|
|||
title: string,
|
||||
titleLoading: string,
|
||||
message: string,
|
||||
startLoading: boolean,
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
|
@ -19,8 +20,16 @@ type State = {
|
|||
|
||||
class LoadingConfirmDialog extends React.PureComponent<Props, State> {
|
||||
|
||||
static defaultProps = {
|
||||
title: '',
|
||||
message: '',
|
||||
onDismiss: () => {},
|
||||
onAccept: () => {return Promise.resolve()},
|
||||
startLoading: false,
|
||||
}
|
||||
|
||||
state = {
|
||||
loading: false,
|
||||
loading: this.props.startLoading,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -65,6 +65,17 @@ class CustomTabBar extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
onItemLongPress(route: Object) {
|
||||
const event = this.props.navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
if (route.name === "home" && !event.defaultPrevented) {
|
||||
this.props.navigation.navigate('tetris');
|
||||
}
|
||||
}
|
||||
|
||||
tabBarIcon = (route, focused) => {
|
||||
let icon = TAB_ICONS[route.name];
|
||||
icon = focused ? icon : icon + ('-outline');
|
||||
|
|
@ -93,12 +104,8 @@ class CustomTabBar extends React.Component<Props, State> {
|
|||
|
||||
const onPress = () => this.onItemPress(route, state.index, index);
|
||||
|
||||
const onLongPress = () => {
|
||||
this.props.navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
});
|
||||
};
|
||||
const onLongPress = () => this.onItemLongPress(route);
|
||||
|
||||
if (isFocused) {
|
||||
const stackState = route.state;
|
||||
const stackRoute = route.state ? stackState.routes[stackState.index] : undefined;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Image, View} from "react-native";
|
||||
import {FAB, withTheme} from 'react-native-paper';
|
||||
import {Image, Platform, View} from "react-native";
|
||||
import {FAB, TouchableRipple, withTheme} from 'react-native-paper';
|
||||
import * as Animatable from "react-native-animatable";
|
||||
import CustomTabBar from "./CustomTabBar";
|
||||
|
||||
type Props = {
|
||||
focused: boolean,
|
||||
|
|
@ -65,23 +66,38 @@ class TabHomeIcon extends React.Component<Props> {
|
|||
render(): React$Node {
|
||||
const props = this.props;
|
||||
return (
|
||||
<View style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<AnimatedFAB
|
||||
duration={200}
|
||||
easing={"ease-out"}
|
||||
animation={props.focused ? "fabFocusIn" : "fabFocusOut"}
|
||||
useNativeDriver
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<TouchableRipple
|
||||
onPress={props.onPress}
|
||||
onLongPress={props.onLongPress}
|
||||
icon={this.iconRender}
|
||||
borderless={true}
|
||||
rippleColor={Platform.OS === 'android' ? this.props.theme.colors.primary : 'transparent'}
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
}}/>
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: CustomTabBar.TAB_BAR_HEIGHT + 30,
|
||||
marginBottom: -15,
|
||||
}}
|
||||
>
|
||||
<AnimatedFAB
|
||||
duration={200}
|
||||
easing={"ease-out"}
|
||||
animation={props.focused ? "fabFocusIn" : "fabFocusOut"}
|
||||
icon={this.iconRender}
|
||||
style={{
|
||||
marginTop: 15,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
}}/>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,10 +69,11 @@ class TabIcon extends React.Component<Props> {
|
|||
<TouchableRipple
|
||||
onPress={props.onPress}
|
||||
onLongPress={props.onLongPress}
|
||||
borderless={true}
|
||||
rippleColor={this.props.theme.colors.primary}
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItem: 'center',
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,38 @@ import {createStackNavigator, TransitionPresets} from "@react-navigation/stack";
|
|||
import i18n from "i18n-js";
|
||||
import TabNavigator from "./TabNavigator";
|
||||
import TetrisScreen from "../screens/Tetris/TetrisScreen";
|
||||
import VoteScreen from "../screens/Amicale/VoteScreen";
|
||||
import LoginScreen from "../screens/Amicale/LoginScreen";
|
||||
import {Platform} from "react-native";
|
||||
import AvailableRoomScreen from "../screens/Websites/AvailableRoomScreen";
|
||||
import BibScreen from "../screens/Websites/BibScreen";
|
||||
import SelfMenuScreen from "../screens/Services/SelfMenuScreen";
|
||||
import ProximoMainScreen from "../screens/Proximo/ProximoMainScreen";
|
||||
import ProximoListScreen from "../screens/Proximo/ProximoListScreen";
|
||||
import ProximoAboutScreen from "../screens/Proximo/ProximoAboutScreen";
|
||||
import {AmicaleWebsiteScreen} from "../screens/Websites/AmicaleWebsiteScreen";
|
||||
import {ElusEtudiantsWebsiteScreen} from "../screens/Websites/ElusEtudiantsWebsiteScreen";
|
||||
import {WiketudWebsiteScreen} from "../screens/Websites/WiketudWebsiteScreen";
|
||||
import {TutorInsaWebsiteScreen} from "../screens/Websites/TutorInsaWebsiteScreen";
|
||||
import {ENTWebsiteScreen} from "../screens/Websites/ENTWebsiteScreen";
|
||||
import {BlueMindWebsiteScreen} from "../screens/Websites/BlueMindWebsiteScreen";
|
||||
import ProfileScreen from "../screens/Amicale/ProfileScreen";
|
||||
import ClubListScreen from "../screens/Amicale/Clubs/ClubListScreen";
|
||||
import ClubAboutScreen from "../screens/Amicale/Clubs/ClubAboutScreen";
|
||||
import ClubDisplayScreen from "../screens/Amicale/Clubs/ClubDisplayScreen";
|
||||
import {createScreenCollapsibleStack, getWebsiteStack} from "../utils/CollapsibleUtils";
|
||||
|
||||
const modalTransition = Platform.OS === 'ios' ? TransitionPresets.ModalPresentationIOS : TransitionPresets.ModalSlideFromBottomIOS;
|
||||
|
||||
const screenTransition = TransitionPresets.SlideFromRightIOS;
|
||||
|
||||
const defaultScreenOptions = {
|
||||
gestureEnabled: true,
|
||||
cardOverlayEnabled: true,
|
||||
...TransitionPresets.SlideFromRightIOS,
|
||||
...screenTransition,
|
||||
};
|
||||
|
||||
|
||||
const MainStack = createStackNavigator();
|
||||
|
||||
function MainStackComponent(props: { createTabNavigator: () => React.Node }) {
|
||||
|
|
@ -30,6 +55,7 @@ function MainStackComponent(props: { createTabNavigator: () => React.Node }) {
|
|||
component={props.createTabNavigator}
|
||||
options={{
|
||||
headerShown: false,
|
||||
title: i18n.t('screens.home'),
|
||||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
|
|
@ -67,6 +93,71 @@ function MainStackComponent(props: { createTabNavigator: () => React.Node }) {
|
|||
title: i18n.t("game.title"),
|
||||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name="login"
|
||||
component={LoginScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.login'),
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* INSA */}
|
||||
{getWebsiteStack("available-rooms", MainStack, AvailableRoomScreen, i18n.t('screens.availableRooms'))}
|
||||
{getWebsiteStack("bib", MainStack, BibScreen, i18n.t('screens.bib'))}
|
||||
{createScreenCollapsibleStack("self-menu", MainStack, SelfMenuScreen, i18n.t('screens.menuSelf'))}
|
||||
|
||||
{/* STUDENTS */}
|
||||
{createScreenCollapsibleStack("proximo", MainStack, ProximoMainScreen, i18n.t('screens.proximo'))}
|
||||
{createScreenCollapsibleStack(
|
||||
"proximo-list",
|
||||
MainStack,
|
||||
ProximoListScreen,
|
||||
i18n.t('screens.proximoArticles'),
|
||||
true,
|
||||
{...screenTransition},
|
||||
)}
|
||||
<MainStack.Screen
|
||||
name="proximo-about"
|
||||
component={ProximoAboutScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.proximo'),
|
||||
...modalTransition,
|
||||
}}
|
||||
/>
|
||||
{getWebsiteStack("amicale-website", MainStack, AmicaleWebsiteScreen, i18n.t('screens.amicaleWebsite'))}
|
||||
{getWebsiteStack("elus-etudiants", MainStack, ElusEtudiantsWebsiteScreen, "Élus Étudiants")}
|
||||
{getWebsiteStack("wiketud", MainStack, WiketudWebsiteScreen, "Wiketud")}
|
||||
{getWebsiteStack("tutorinsa", MainStack, TutorInsaWebsiteScreen, "Tutor'INSA")}
|
||||
{getWebsiteStack("ent", MainStack, ENTWebsiteScreen, i18n.t('screens.ent'))}
|
||||
{getWebsiteStack("bluemind", MainStack, BlueMindWebsiteScreen, i18n.t('screens.bluemind'))}
|
||||
|
||||
|
||||
{/* AMICALE */}
|
||||
{createScreenCollapsibleStack("profile", MainStack, ProfileScreen, i18n.t('screens.profile'))}
|
||||
{createScreenCollapsibleStack("club-list", MainStack, ClubListScreen, i18n.t('clubs.clubList'))}
|
||||
<MainStack.Screen
|
||||
name="club-information"
|
||||
component={ClubDisplayScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.clubDisplayScreen'),
|
||||
...modalTransition,
|
||||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name="club-about"
|
||||
component={ClubAboutScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.clubsAbout'),
|
||||
...modalTransition,
|
||||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name="vote"
|
||||
component={VoteScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.vote'),
|
||||
}}
|
||||
/>
|
||||
</MainStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ import PlanningScreen from '../screens/Planning/PlanningScreen';
|
|||
import PlanningDisplayScreen from '../screens/Planning/PlanningDisplayScreen';
|
||||
import ProxiwashScreen from '../screens/Proxiwash/ProxiwashScreen';
|
||||
import ProxiwashAboutScreen from '../screens/Proxiwash/ProxiwashAboutScreen';
|
||||
import ProximoMainScreen from '../screens/Proximo/ProximoMainScreen';
|
||||
import ProximoListScreen from "../screens/Proximo/ProximoListScreen";
|
||||
import ProximoAboutScreen from "../screens/Proximo/ProximoAboutScreen";
|
||||
import PlanexScreen from '../screens/Planex/PlanexScreen';
|
||||
import AsyncStorageManager from "../managers/AsyncStorageManager";
|
||||
import {useTheme} from 'react-native-paper';
|
||||
|
|
@ -21,23 +18,10 @@ import FeedItemScreen from "../screens/Home/FeedItemScreen";
|
|||
import {createCollapsibleStack} from "react-navigation-collapsible";
|
||||
import GroupSelectionScreen from "../screens/Planex/GroupSelectionScreen";
|
||||
import CustomTabBar from "../components/Tabbar/CustomTabBar";
|
||||
import SelfMenuScreen from "../screens/Services/SelfMenuScreen";
|
||||
import AvailableRoomScreen from "../screens/Websites/AvailableRoomScreen";
|
||||
import BibScreen from "../screens/Websites/BibScreen";
|
||||
import {AmicaleWebsiteScreen} from "../screens/Websites/AmicaleWebsiteScreen";
|
||||
import {ElusEtudiantsWebsiteScreen} from "../screens/Websites/ElusEtudiantsWebsiteScreen";
|
||||
import {WiketudWebsiteScreen} from "../screens/Websites/WiketudWebsiteScreen";
|
||||
import {TutorInsaWebsiteScreen} from "../screens/Websites/TutorInsaWebsiteScreen";
|
||||
import {ENTWebsiteScreen} from "../screens/Websites/ENTWebsiteScreen";
|
||||
import {BlueMindWebsiteScreen} from "../screens/Websites/BlueMindWebsiteScreen";
|
||||
import LoginScreen from "../screens/Amicale/LoginScreen";
|
||||
import ProfileScreen from "../screens/Amicale/ProfileScreen";
|
||||
import ClubListScreen from "../screens/Amicale/Clubs/ClubListScreen";
|
||||
import ClubAboutScreen from "../screens/Amicale/Clubs/ClubAboutScreen";
|
||||
import VoteScreen from "../screens/Amicale/VoteScreen";
|
||||
import AmicaleContactScreen from "../screens/Amicale/AmicaleContactScreen";
|
||||
import WebsitesHomeScreen from "../screens/Services/ServicesScreen";
|
||||
import ServicesSectionScreen from "../screens/Services/ServicesSectionScreen";
|
||||
import AmicaleContactScreen from "../screens/Amicale/AmicaleContactScreen";
|
||||
import {createScreenCollapsibleStack, getWebsiteStack} from "../utils/CollapsibleUtils";
|
||||
|
||||
const defaultScreenOptions = {
|
||||
gestureEnabled: true,
|
||||
|
|
@ -47,41 +31,6 @@ const defaultScreenOptions = {
|
|||
|
||||
const modalTransition = Platform.OS === 'ios' ? TransitionPresets.ModalPresentationIOS : TransitionPresets.ModalSlideFromBottomIOS;
|
||||
|
||||
const screenTransition = Platform.OS === 'ios' ? TransitionPresets.SlideFromRightIOS : TransitionPresets.ScaleFromCenterAndroid;
|
||||
|
||||
function createScreenCollapsibleStack(
|
||||
name: string,
|
||||
Stack: any,
|
||||
component: any,
|
||||
title: string,
|
||||
useNativeDriver?: boolean,
|
||||
options?: { [key: string]: any }) {
|
||||
const {colors} = useTheme();
|
||||
const screenOptions = options != null ? options : {};
|
||||
return createCollapsibleStack(
|
||||
<Stack.Screen
|
||||
name={name}
|
||||
component={component}
|
||||
options={{
|
||||
title: title,
|
||||
headerStyle: {
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
...screenOptions,
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
collapsedColor: 'transparent',
|
||||
useNativeDriver: useNativeDriver != null ? useNativeDriver : true, // native driver does not work with webview
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function getWebsiteStack(name: string, Stack: any, component: any, title: string) {
|
||||
return createScreenCollapsibleStack(name, Stack, component, title, false);
|
||||
}
|
||||
|
||||
|
||||
const ServicesStack = createStackNavigator();
|
||||
|
||||
function ServicesStackComponent() {
|
||||
|
|
@ -93,64 +42,7 @@ function ServicesStackComponent() {
|
|||
>
|
||||
{createScreenCollapsibleStack("index", ServicesStack, WebsitesHomeScreen, i18n.t('screens.services'))}
|
||||
{createScreenCollapsibleStack("services-section", ServicesStack, ServicesSectionScreen, "SECTION")}
|
||||
|
||||
{/* INSA */}
|
||||
{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'))}
|
||||
|
||||
{/* STUDENTS */}
|
||||
{createScreenCollapsibleStack("proximo", ServicesStack, ProximoMainScreen, i18n.t('screens.proximo'))}
|
||||
{createScreenCollapsibleStack(
|
||||
"proximo-list",
|
||||
ServicesStack,
|
||||
ProximoListScreen,
|
||||
i18n.t('screens.proximoArticles'),
|
||||
true,
|
||||
{...screenTransition},
|
||||
)}
|
||||
<ServicesStack.Screen
|
||||
name="proximo-about"
|
||||
component={ProximoAboutScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.proximo'),
|
||||
...modalTransition,
|
||||
}}
|
||||
/>
|
||||
{getWebsiteStack("amicale-website", ServicesStack, AmicaleWebsiteScreen, i18n.t('screens.amicaleWebsite'))}
|
||||
{getWebsiteStack("elus-etudiants", ServicesStack, ElusEtudiantsWebsiteScreen, "Élus Étudiants")}
|
||||
{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={{
|
||||
title: i18n.t('screens.clubsAbout'),
|
||||
...modalTransition,
|
||||
}}
|
||||
/>
|
||||
<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'),
|
||||
}}
|
||||
/>
|
||||
{createScreenCollapsibleStack("amicale-contact", ServicesStack, AmicaleContactScreen, i18n.t('screens.amicaleAbout'), true, {...modalTransition})}
|
||||
</ServicesStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
|
@ -231,7 +123,7 @@ function HomeStackComponent(initialRoute: string | null, defaultData: { [key: st
|
|||
initialParams={params}
|
||||
/>,
|
||||
{
|
||||
collapsedColor: 'transparent',
|
||||
collapsedColor: colors.surface,
|
||||
useNativeDriver: true,
|
||||
}
|
||||
)}
|
||||
|
|
@ -267,8 +159,6 @@ function HomeStackComponent(initialRoute: string | null, defaultData: { [key: st
|
|||
...modalTransition,
|
||||
}}
|
||||
/>
|
||||
{createScreenCollapsibleStack("self-menu", HomeStack, SelfMenuScreen, i18n.t('screens.menuSelf'), true, {...modalTransition})}
|
||||
{createScreenCollapsibleStack("login", HomeStack, LoginScreen, i18n.t('screens.login'))}
|
||||
</HomeStack.Navigator>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {FlatList, Image, View} from 'react-native';
|
||||
import {Animated, FlatList, Image, View} from 'react-native';
|
||||
import {Card, List, Text, withTheme} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
import {Linking} from "expo";
|
||||
import {Collapsible} from "react-navigation-collapsible";
|
||||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
||||
import {withCollapsible} from "../../utils/withCollapsible";
|
||||
|
||||
type Props = {};
|
||||
type Props = {
|
||||
collapsibleStack: Collapsible
|
||||
};
|
||||
|
||||
type State = {};
|
||||
|
||||
|
|
@ -123,14 +128,22 @@ class AmicaleContactScreen extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
|
||||
return (
|
||||
//$FlowFixMe
|
||||
<FlatList
|
||||
<Animated.FlatList
|
||||
data={[{key: "1"}]}
|
||||
renderItem={this.getScreen}
|
||||
// Animations
|
||||
onScroll={onScroll}
|
||||
contentContainerStyle={{
|
||||
paddingTop: containerPaddingTop,
|
||||
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT,
|
||||
minHeight: '100%'
|
||||
}}
|
||||
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(AmicaleContactScreen);
|
||||
export default withCollapsible(withTheme(AmicaleContactScreen));
|
||||
|
|
|
|||
|
|
@ -2,22 +2,36 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import {ScrollView, View} from 'react-native';
|
||||
import {Avatar, Card, Chip, Paragraph, withTheme} from 'react-native-paper';
|
||||
import {Avatar, Button, Card, Chip, Paragraph, withTheme} from 'react-native-paper';
|
||||
import ImageModal from 'react-native-image-modal';
|
||||
import i18n from "i18n-js";
|
||||
import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen";
|
||||
import CustomHTML from "../../../components/Overrides/CustomHTML";
|
||||
import CustomTabBar from "../../../components/Tabbar/CustomTabBar";
|
||||
import type {category, club} from "./ClubListScreen";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import {Linking} from "expo";
|
||||
import {ERROR_TYPE} from "../../../utils/WebData";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
route: Object
|
||||
navigation: StackNavigationProp,
|
||||
route: {
|
||||
params?: {
|
||||
data?: club,
|
||||
categories?: Array<category>,
|
||||
clubId?: number,
|
||||
}, ...
|
||||
},
|
||||
theme: CustomTheme
|
||||
};
|
||||
|
||||
type State = {
|
||||
imageModalVisible: boolean,
|
||||
};
|
||||
|
||||
const AMICALE_MAIL = "clubs@amicale-insat.fr";
|
||||
|
||||
/**
|
||||
* Class defining a club event information page.
|
||||
* If called with data and categories navigation parameters, will use those to display the data.
|
||||
|
|
@ -25,32 +39,30 @@ type State = {
|
|||
*/
|
||||
class ClubDisplayScreen extends React.Component<Props, State> {
|
||||
|
||||
displayData: Object;
|
||||
categories: Object | null;
|
||||
displayData: club | null;
|
||||
categories: Array<category> | null;
|
||||
clubId: number;
|
||||
|
||||
shouldFetchData: boolean;
|
||||
|
||||
colors: Object;
|
||||
|
||||
state = {
|
||||
imageModalVisible: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
|
||||
if (this.props.route.params.data !== undefined && this.props.route.params.categories !== undefined) {
|
||||
this.displayData = this.props.route.params.data;
|
||||
this.categories = this.props.route.params.categories;
|
||||
this.clubId = this.props.route.params.data.id;
|
||||
this.shouldFetchData = false;
|
||||
} else {
|
||||
this.displayData = null;
|
||||
this.categories = null;
|
||||
this.clubId = this.props.route.params.clubId;
|
||||
this.shouldFetchData = true;
|
||||
if (this.props.route.params != null) {
|
||||
if (this.props.route.params.data != null && this.props.route.params.categories != null) {
|
||||
this.displayData = this.props.route.params.data;
|
||||
this.categories = this.props.route.params.categories;
|
||||
this.clubId = this.props.route.params.data.id;
|
||||
this.shouldFetchData = false;
|
||||
} else if (this.props.route.params.clubId != null) {
|
||||
this.displayData = null;
|
||||
this.categories = null;
|
||||
this.clubId = this.props.route.params.clubId;
|
||||
this.shouldFetchData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +76,7 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
return "";
|
||||
}
|
||||
|
||||
getCategoriesRender(categories: Array<number | null>) {
|
||||
getCategoriesRender(categories: [number, number]) {
|
||||
if (this.categories === null)
|
||||
return null;
|
||||
|
||||
|
|
@ -84,7 +96,7 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
return <View style={{flexDirection: 'row', marginTop: 5}}>{final}</View>;
|
||||
}
|
||||
|
||||
getManagersRender(resp: Array<string>) {
|
||||
getManagersRender(resp: Array<string>, email: string | null) {
|
||||
let final = [];
|
||||
for (let i = 0; i < resp.length; i++) {
|
||||
final.push(<Paragraph key={i.toString()}>{resp[i]}</Paragraph>)
|
||||
|
|
@ -98,56 +110,81 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
left={(props) => <Avatar.Icon
|
||||
{...props}
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
color={hasManagers ? this.colors.success : this.colors.primary}
|
||||
color={hasManagers ? this.props.theme.colors.success : this.props.theme.colors.primary}
|
||||
icon="account-tie"/>}
|
||||
/>
|
||||
<Card.Content>
|
||||
{final}
|
||||
{this.getEmailButton(email, hasManagers)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
getEmailButton(email: string | null, hasManagers: boolean) {
|
||||
const destinationEmail = email != null && hasManagers
|
||||
? email
|
||||
: AMICALE_MAIL;
|
||||
const text = email != null && hasManagers
|
||||
? i18n.t("clubs.clubContact")
|
||||
: i18n.t("clubs.amicaleContact");
|
||||
return (
|
||||
<Card.Actions>
|
||||
<Button
|
||||
icon="email"
|
||||
mode="contained"
|
||||
onPress={() => Linking.openURL('mailto:' + destinationEmail)}
|
||||
style={{marginLeft: 'auto'}}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
);
|
||||
}
|
||||
|
||||
updateHeaderTitle(data: Object) {
|
||||
this.props.navigation.setOptions({title: data.name})
|
||||
}
|
||||
|
||||
getScreen = (response: Array<Object>) => {
|
||||
let data = response[0];
|
||||
let data: club = response[0];
|
||||
this.updateHeaderTitle(data);
|
||||
if (data != null) {
|
||||
return (
|
||||
<ScrollView style={{paddingLeft: 5, paddingRight: 5}}>
|
||||
{this.getCategoriesRender(data.category)}
|
||||
{data.logo !== null ?
|
||||
<View style={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<ImageModal
|
||||
resizeMode="contain"
|
||||
imageBackgroundColor={this.props.theme.colors.background}
|
||||
style={{
|
||||
width: 300,
|
||||
height: 300,
|
||||
}}
|
||||
source={{
|
||||
uri: data.logo,
|
||||
}}
|
||||
/></View>
|
||||
: <View/>}
|
||||
|
||||
return (
|
||||
<ScrollView style={{paddingLeft: 5, paddingRight: 5}}>
|
||||
{this.getCategoriesRender(data.category)}
|
||||
{data.logo !== null ?
|
||||
<View style={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<ImageModal
|
||||
resizeMode="contain"
|
||||
imageBackgroundColor={this.colors.background}
|
||||
style={{
|
||||
width: 300,
|
||||
height: 300,
|
||||
}}
|
||||
source={{
|
||||
uri: data.logo,
|
||||
}}
|
||||
/></View>
|
||||
: <View/>}
|
||||
{data.description !== null ?
|
||||
// Surround description with div to allow text styling if the description is not html
|
||||
<Card.Content>
|
||||
<CustomHTML html={data.description}/>
|
||||
</Card.Content>
|
||||
: <View/>}
|
||||
{this.getManagersRender(data.responsibles, data.email)}
|
||||
q </ScrollView>
|
||||
);
|
||||
} else
|
||||
return null;
|
||||
|
||||
{data.description !== null ?
|
||||
// Surround description with div to allow text styling if the description is not html
|
||||
<Card.Content>
|
||||
<CustomHTML html={data.description}/>
|
||||
</Card.Content>
|
||||
: <View/>}
|
||||
{this.getManagersRender(data.responsibles)}
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -162,6 +199,14 @@ class ClubDisplayScreen extends React.Component<Props, State> {
|
|||
}
|
||||
]}
|
||||
renderFunction={this.getScreen}
|
||||
errorViewOverride={[
|
||||
{
|
||||
errorCode: ERROR_TYPE.BAD_INPUT,
|
||||
message: i18n.t("clubs.invalidClub"),
|
||||
icon: "account-question",
|
||||
showRetryButton: false
|
||||
}
|
||||
]}
|
||||
/>;
|
||||
else
|
||||
return this.getScreen([this.displayData]);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export type club = {
|
|||
name: string,
|
||||
description: string,
|
||||
logo: string,
|
||||
email:string,
|
||||
email: string | null,
|
||||
category: [number, number],
|
||||
responsibles: Array<string>,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -192,11 +192,11 @@ class HomeScreen extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
onProximoClick = () => {
|
||||
this.props.navigation.navigate('services', {screen: "index"});
|
||||
this.props.navigation.navigate("proximo");
|
||||
};
|
||||
|
||||
onTutorInsaClick = () => {
|
||||
this.props.navigation.navigate('services', {screen: "index"});
|
||||
this.props.navigation.navigate("tutorinsa");
|
||||
};
|
||||
|
||||
onMenuClick = () => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {Linking} from "expo";
|
|||
import AlertDialog from "../../components/Dialogs/AlertDialog";
|
||||
import i18n from 'i18n-js';
|
||||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
||||
import LoadingConfirmDialog from "../../components/Dialogs/LoadingConfirmDialog";
|
||||
|
||||
type Props = {};
|
||||
type State = {
|
||||
|
|
@ -18,6 +19,7 @@ type State = {
|
|||
dialogVisible: boolean,
|
||||
dialogTitle: string,
|
||||
dialogMessage: string,
|
||||
loading: boolean,
|
||||
};
|
||||
|
||||
class ScannerScreen extends React.Component<Props, State> {
|
||||
|
|
@ -28,6 +30,7 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
dialogVisible: false,
|
||||
dialogTitle: "",
|
||||
dialogMessage: "",
|
||||
loading: false,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
|
@ -46,7 +49,7 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
if (!URLHandler.isUrlValid(data))
|
||||
this.showErrorDialog();
|
||||
else {
|
||||
this.setState({scanned: true});
|
||||
this.showOpeningDialog();
|
||||
Linking.openURL(data);
|
||||
}
|
||||
};
|
||||
|
|
@ -108,6 +111,13 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
});
|
||||
};
|
||||
|
||||
showOpeningDialog = () => {
|
||||
this.setState({
|
||||
loading: true,
|
||||
scanned: true,
|
||||
});
|
||||
};
|
||||
|
||||
showErrorDialog() {
|
||||
this.setState({
|
||||
dialogVisible: true,
|
||||
|
|
@ -166,6 +176,11 @@ class ScannerScreen extends React.Component<Props, State> {
|
|||
title={this.state.dialogTitle}
|
||||
message={this.state.dialogMessage}
|
||||
/>
|
||||
<LoadingConfirmDialog
|
||||
visible={this.state.loading}
|
||||
titleLoading={i18n.t("general.loading")}
|
||||
startLoading={true}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import {Card, withTheme} from 'react-native-paper';
|
|||
import DateManager from "../../managers/DateManager";
|
||||
import ImageModal from 'react-native-image-modal';
|
||||
import BasicLoadingScreen from "../../components/Screens/BasicLoadingScreen";
|
||||
import {apiRequest} from "../../utils/WebData";
|
||||
import {apiRequest, ERROR_TYPE} from "../../utils/WebData";
|
||||
import ErrorView from "../../components/Screens/ErrorView";
|
||||
import CustomHTML from "../../components/Overrides/CustomHTML";
|
||||
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
|
|
@ -113,13 +114,20 @@ class PlanningDisplayScreen extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
getErrorView() {
|
||||
if (this.errorCode === ERROR_TYPE.BAD_INPUT)
|
||||
return <ErrorView {...this.props} showRetryButton={false} message={i18n.t("planningScreen.invalidEvent")} icon={"calendar-remove"}/>;
|
||||
else
|
||||
return <ErrorView {...this.props} errorCode={this.errorCode} onRefresh={this.fetchData}/>;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.loading)
|
||||
return <BasicLoadingScreen/>;
|
||||
else if (this.errorCode === 0)
|
||||
return this.getContent();
|
||||
else
|
||||
return <ErrorView {...this.props} errorCode={this.errorCode} onRefresh={this.fetchData}/>;
|
||||
return this.getErrorView();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {Avatar, Button, Card, Divider, List, Title, TouchableRipple, withTheme}
|
|||
import type {CustomTheme} from "../../managers/ThemeManager";
|
||||
import ConnectionManager from "../../managers/ConnectionManager";
|
||||
import i18n from 'i18n-js';
|
||||
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
|
|
@ -67,12 +68,6 @@ class ServicesScreen extends React.Component<Props, State> {
|
|||
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",
|
||||
|
|
@ -174,9 +169,18 @@ class ServicesScreen extends React.Component<Props, State> {
|
|||
|
||||
componentDidMount() {
|
||||
this.props.navigation.addListener('focus', this.onFocus);
|
||||
|
||||
this.props.navigation.setOptions({
|
||||
headerRight: this.getAboutButton,
|
||||
});
|
||||
}
|
||||
|
||||
getAboutButton = () =>
|
||||
<MaterialHeaderButtons>
|
||||
<Item title="information" iconName="information" onPress={this.onAboutPress}/>
|
||||
</MaterialHeaderButtons>;
|
||||
|
||||
onAboutPress = () => this.props.navigation.navigate('amicale-contact');
|
||||
|
||||
onFocus = () => {
|
||||
this.handleNavigationParams();
|
||||
this.setState({isLoggedIn: ConnectionManager.getInstance().isLoggedIn()})
|
||||
|
|
|
|||
38
src/utils/CollapsibleUtils.js
Normal file
38
src/utils/CollapsibleUtils.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {useTheme} from "react-native-paper";
|
||||
import {createCollapsibleStack} from "react-navigation-collapsible";
|
||||
import StackNavigator, {StackNavigationOptions} from "@react-navigation/stack";
|
||||
|
||||
export function createScreenCollapsibleStack(
|
||||
name: string,
|
||||
Stack: StackNavigator,
|
||||
component: React.Node,
|
||||
title: string,
|
||||
useNativeDriver?: boolean,
|
||||
options?: StackNavigationOptions) {
|
||||
const {colors} = useTheme();
|
||||
const screenOptions = options != null ? options : {};
|
||||
return createCollapsibleStack(
|
||||
<Stack.Screen
|
||||
name={name}
|
||||
component={component}
|
||||
options={{
|
||||
title: title,
|
||||
headerStyle: {
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
...screenOptions,
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
collapsedColor: colors.surface,
|
||||
useNativeDriver: useNativeDriver != null ? useNativeDriver : true, // native driver does not work with webview
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function getWebsiteStack(name: string, Stack: any, component: any, title: string) {
|
||||
return createScreenCollapsibleStack(name, Stack, component, title, false);
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ export default class URLHandler {
|
|||
static EVENT_INFO_URL_PATH = "event";
|
||||
|
||||
static CLUB_INFO_ROUTE = "club-information";
|
||||
static EVENT_INFO_ROUTE = "home-planning-information";
|
||||
static EVENT_INFO_ROUTE = "planning-information";
|
||||
|
||||
onInitialURLParsed: Function;
|
||||
onDetectURL: Function;
|
||||
|
|
|
|||
|
|
@ -26,12 +26,6 @@
|
|||
"vote": "Elections",
|
||||
"scanner": "Scanotron 3000"
|
||||
},
|
||||
"sidenav": {
|
||||
"divider1": "Student websites",
|
||||
"divider2": "Services",
|
||||
"divider3": "Personalisation",
|
||||
"divider4": "Amicale"
|
||||
},
|
||||
"intro": {
|
||||
"slide1": {
|
||||
"title": "Welcome to CAMPUS",
|
||||
|
|
@ -261,6 +255,9 @@
|
|||
"managersUnavailable": "This club has no one :(",
|
||||
"categories": "Categories",
|
||||
"categoriesFilterMessage": "Click on a category to filter the list",
|
||||
"clubContact": "Contact the club",
|
||||
"amicaleContact": "Contact the Amicale",
|
||||
"invalidClub": "Could not find the club. Please make sure the club you are trying to access is valid.",
|
||||
"about": {
|
||||
"text": "The clubs, making the campus live, with more than sixty clubs offering various activities! From the philosophy club to the PABI (Production Artisanale de Bière Insaienne), without forgetting the multiple music and dance clubs, you will surely find an activity that suits you!",
|
||||
"title": "A question ?",
|
||||
|
|
@ -388,5 +385,8 @@
|
|||
"students": "Student services",
|
||||
"insa": "INSA services",
|
||||
"notLoggedIn": "Not logged in"
|
||||
},
|
||||
"planningScreen": {
|
||||
"invalidEvent": "Could not find the event. Please make sure the event you are trying to access is valid."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,6 @@
|
|||
"vote": "Élections",
|
||||
"scanner": "Scanotron 3000"
|
||||
},
|
||||
"sidenav": {
|
||||
"divider1": "Sites étudiants",
|
||||
"divider2": "Services",
|
||||
"divider3": "Personnalisation",
|
||||
"divider4": "Amicale"
|
||||
},
|
||||
"intro": {
|
||||
"slide1": {
|
||||
"title": "Bienvenue sur CAMPUS",
|
||||
|
|
@ -261,6 +255,9 @@
|
|||
"managersUnavailable": "Ce club est tout seul :(",
|
||||
"categories": "Catégories",
|
||||
"categoriesFilterMessage": "Cliquez sur une catégorie pour filtrer la liste",
|
||||
"clubContact": "Contacter le club",
|
||||
"amicaleContact": "Contacter l'Amicale",
|
||||
"invalidClub": "Impossible de trouver le club. Merci de vérifier que le club que vous voulez voir est valide.",
|
||||
"about": {
|
||||
"text": "Les clubs, c'est ce qui fait vivre le campus au quotidien, plus d'une soixantaine de clubs qui proposent des activités diverses et variées ! Du club Philosophie au PABI (Production Artisanale de Bière Insaienne), en passant par les multiples clubs de musique et de danse, vous trouverez forcément une activité qui vous permettra de vous épanouir sur le campus !",
|
||||
"title": "Une question ?",
|
||||
|
|
@ -388,5 +385,8 @@
|
|||
"students": "Services étudiants",
|
||||
"insa": "Services de l'INSA",
|
||||
"notLoggedIn": "Non connecté"
|
||||
},
|
||||
"planningScreen": {
|
||||
"invalidEvent": "Impossible de trouver l'événement. Merci de vérifier que l'événement que vous voulez voir est valide."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue