forked from vergnet/application-amicale
Added qr code scanner screen
This commit is contained in:
parent
8e2d1c7a2b
commit
96e9da162e
7 changed files with 264 additions and 25 deletions
|
@ -49,7 +49,9 @@
|
||||||
"react-native-render-html": "^4.1.2",
|
"react-native-render-html": "^4.1.2",
|
||||||
"react-native-safe-area-context": "0.7.3",
|
"react-native-safe-area-context": "0.7.3",
|
||||||
"react-native-screens": "~2.2.0",
|
"react-native-screens": "~2.2.0",
|
||||||
"react-native-webview": "8.1.1"
|
"react-native-webview": "8.1.1",
|
||||||
|
"expo-barcode-scanner": "~8.1.0",
|
||||||
|
"expo-camera": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.8.4",
|
"@babel/cli": "^7.8.4",
|
||||||
|
|
|
@ -17,6 +17,7 @@ import HeaderButton from "../components/Custom/HeaderButton";
|
||||||
import {withTheme} from 'react-native-paper';
|
import {withTheme} from 'react-native-paper';
|
||||||
import i18n from "i18n-js";
|
import i18n from "i18n-js";
|
||||||
import ClubDisplayScreen from "../screens/Amicale/Clubs/ClubDisplayScreen";
|
import ClubDisplayScreen from "../screens/Amicale/Clubs/ClubDisplayScreen";
|
||||||
|
import ScannerScreen from "../screens/ScannerScreen";
|
||||||
|
|
||||||
|
|
||||||
const TAB_ICONS = {
|
const TAB_ICONS = {
|
||||||
|
@ -62,7 +63,7 @@ function ProximoStackComponent() {
|
||||||
<ProximoStack.Screen
|
<ProximoStack.Screen
|
||||||
name="proximo-list"
|
name="proximo-list"
|
||||||
options={{
|
options={{
|
||||||
title: 'Articles'
|
title: i18n.t('screens.proximoArticles')
|
||||||
}}
|
}}
|
||||||
component={ProximoListScreen}
|
component={ProximoListScreen}
|
||||||
/>
|
/>
|
||||||
|
@ -70,7 +71,7 @@ function ProximoStackComponent() {
|
||||||
name="proximo-about"
|
name="proximo-about"
|
||||||
component={ProximoAboutScreen}
|
component={ProximoAboutScreen}
|
||||||
options={{
|
options={{
|
||||||
title: 'Proximo',
|
title: i18n.t('screens.proximo'),
|
||||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -93,7 +94,7 @@ function ProxiwashStackComponent() {
|
||||||
options={({navigation}) => {
|
options={({navigation}) => {
|
||||||
const openDrawer = getDrawerButton.bind(this, navigation);
|
const openDrawer = getDrawerButton.bind(this, navigation);
|
||||||
return {
|
return {
|
||||||
title: 'Proxiwash',
|
title: i18n.t('screens.proxiwash'),
|
||||||
headerLeft: openDrawer
|
headerLeft: openDrawer
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
@ -102,7 +103,7 @@ function ProxiwashStackComponent() {
|
||||||
name="proxiwash-about"
|
name="proxiwash-about"
|
||||||
component={ProxiwashAboutScreen}
|
component={ProxiwashAboutScreen}
|
||||||
options={{
|
options={{
|
||||||
title: 'Proxiwash',
|
title: i18n.t('screens.proxiwash'),
|
||||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -125,7 +126,7 @@ function PlanningStackComponent() {
|
||||||
options={({navigation}) => {
|
options={({navigation}) => {
|
||||||
const openDrawer = getDrawerButton.bind(this, navigation);
|
const openDrawer = getDrawerButton.bind(this, navigation);
|
||||||
return {
|
return {
|
||||||
title: 'Planning',
|
title: i18n.t('screens.planning'),
|
||||||
headerLeft: openDrawer
|
headerLeft: openDrawer
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
@ -134,7 +135,7 @@ function PlanningStackComponent() {
|
||||||
name="planning-information"
|
name="planning-information"
|
||||||
component={PlanningDisplayScreen}
|
component={PlanningDisplayScreen}
|
||||||
options={{
|
options={{
|
||||||
title: 'Details',
|
title: i18n.t('screens.planningDisplayScreen'),
|
||||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -171,7 +172,7 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
|
||||||
name="planning-information"
|
name="planning-information"
|
||||||
component={PlanningDisplayScreen}
|
component={PlanningDisplayScreen}
|
||||||
options={{
|
options={{
|
||||||
title: 'Details',
|
title: i18n.t('screens.planningDisplayScreen'),
|
||||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -180,7 +181,17 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
|
||||||
component={ClubDisplayScreen}
|
component={ClubDisplayScreen}
|
||||||
options={({navigation}) => {
|
options={({navigation}) => {
|
||||||
return {
|
return {
|
||||||
title: "",
|
title: '',
|
||||||
|
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<HomeStack.Screen
|
||||||
|
name="scanner"
|
||||||
|
component={ScannerScreen}
|
||||||
|
options={({navigation}) => {
|
||||||
|
return {
|
||||||
|
title: i18n.t('screens.scanner'),
|
||||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {View} from 'react-native';
|
import {StyleSheet, View} from 'react-native';
|
||||||
import i18n from "i18n-js";
|
import i18n from "i18n-js";
|
||||||
import DashboardItem from "../components/Home/EventDashboardItem";
|
import DashboardItem from "../components/Home/EventDashboardItem";
|
||||||
import WebSectionList from "../components/Lists/WebSectionList";
|
import WebSectionList from "../components/Lists/WebSectionList";
|
||||||
import {Text, withTheme} from 'react-native-paper';
|
import {FAB, Text, withTheme} from 'react-native-paper';
|
||||||
import FeedItem from "../components/Home/FeedItem";
|
import FeedItem from "../components/Home/FeedItem";
|
||||||
import SquareDashboardItem from "../components/Home/SmallDashboardItem";
|
import SquareDashboardItem from "../components/Home/SmallDashboardItem";
|
||||||
import PreviewEventDashboardItem from "../components/Home/PreviewEventDashboardItem";
|
import PreviewEventDashboardItem from "../components/Home/PreviewEventDashboardItem";
|
||||||
|
@ -467,9 +467,12 @@ class HomeScreen extends React.Component<Props> {
|
||||||
this.getDashboardItem(item) : this.getFeedItem(item));
|
this.getDashboardItem(item) : this.getFeedItem(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openScanner = () => this.props.navigation.navigate("scanner");
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const nav = this.props.navigation;
|
const nav = this.props.navigation;
|
||||||
return (
|
return (
|
||||||
|
<View>
|
||||||
<WebSectionList
|
<WebSectionList
|
||||||
createDataset={this.createDataset}
|
createDataset={this.createDataset}
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
|
@ -477,8 +480,23 @@ class HomeScreen extends React.Component<Props> {
|
||||||
refreshOnFocus={true}
|
refreshOnFocus={true}
|
||||||
fetchUrl={DATA_URL}
|
fetchUrl={DATA_URL}
|
||||||
renderItem={this.getRenderItem}/>
|
renderItem={this.getRenderItem}/>
|
||||||
|
<FAB
|
||||||
|
style={styles.fab}
|
||||||
|
icon="qrcode-scan"
|
||||||
|
onPress={this.openScanner}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
fab: {
|
||||||
|
position: 'absolute',
|
||||||
|
margin: 16,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default withTheme(HomeScreen);
|
export default withTheme(HomeScreen);
|
||||||
|
|
192
src/screens/ScannerScreen.js
Normal file
192
src/screens/ScannerScreen.js
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import {StyleSheet, View} from "react-native";
|
||||||
|
import {Text, withTheme} from 'react-native-paper';
|
||||||
|
import {BarCodeScanner} from "expo-barcode-scanner";
|
||||||
|
import {Camera} from 'expo-camera';
|
||||||
|
import URLHandler from "../utils/URLHandler";
|
||||||
|
import {Linking} from "expo";
|
||||||
|
import AlertDialog from "../components/Dialog/AlertDialog";
|
||||||
|
import i18n from 'i18n-js';
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
type State = {
|
||||||
|
hasPermission: boolean,
|
||||||
|
scanned: boolean,
|
||||||
|
dialogVisible: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScannerScreen extends React.Component<Props, State> {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hasPermission: false,
|
||||||
|
scanned: false,
|
||||||
|
dialogVisible: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
Camera.requestPermissionsAsync().then(this.updatePermissionStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePermissionStatus = ({status}) => this.setState({hasPermission: status === "granted"});
|
||||||
|
|
||||||
|
|
||||||
|
handleCodeScanned = ({type, data}) => {
|
||||||
|
this.setState({scanned: true});
|
||||||
|
if (!URLHandler.isUrlValid(data))
|
||||||
|
this.showErrorDialog();
|
||||||
|
else
|
||||||
|
Linking.openURL(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
getPermissionScreen() {
|
||||||
|
return <Text>PLS</Text>
|
||||||
|
}
|
||||||
|
|
||||||
|
getOverlay() {
|
||||||
|
return (
|
||||||
|
<View style={{flex: 1}}>
|
||||||
|
<View style={{flex: 1}}>
|
||||||
|
<View style={{...overlayBackground, top: 0, height: '10%', width: '80%', left: '10%'}}/>
|
||||||
|
<View style={{...overlayBackground, left: 0, width: '10%', height: '100%'}}/>
|
||||||
|
<View style={{...overlayBackground, right: 0, width: '10%', height: '100%'}}/>
|
||||||
|
<View style={{...overlayBackground, bottom: 0, height: '10%', width: '80%', left: '10%'}}/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.overlayTopLeft}>
|
||||||
|
<View style={{...overlayHorizontalLineStyle, top: 0}}/>
|
||||||
|
<View style={{...overlayVerticalLineStyle, left: 0}}/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.overlayTopRight}>
|
||||||
|
<View style={{...overlayHorizontalLineStyle, top: 0}}/>
|
||||||
|
<View style={{...overlayVerticalLineStyle, right: 0}}/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.overlayBottomLeft}>
|
||||||
|
<View style={{...overlayHorizontalLineStyle, bottom: 0}}/>
|
||||||
|
<View style={{...overlayVerticalLineStyle, left: 0}}/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.overlayBottomRight}>
|
||||||
|
<View style={{...overlayHorizontalLineStyle, bottom: 0}}/>
|
||||||
|
<View style={{...overlayVerticalLineStyle, right: 0}}/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showErrorDialog() {
|
||||||
|
this.setState({dialogVisible: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDialogDismiss = () => this.setState({
|
||||||
|
dialogVisible: false,
|
||||||
|
scanned: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
getScanner() {
|
||||||
|
return (
|
||||||
|
<View style={styles.cameraContainer}>
|
||||||
|
<AlertDialog
|
||||||
|
visible={this.state.dialogVisible}
|
||||||
|
onDismiss={this.onDialogDismiss}
|
||||||
|
title={i18n.t("scannerScreen.errorTitle")}
|
||||||
|
message={i18n.t("scannerScreen.errorMessage")}
|
||||||
|
/>
|
||||||
|
<Camera
|
||||||
|
onBarCodeScanned={this.state.scanned ? undefined : this.handleCodeScanned}
|
||||||
|
type={Camera.Constants.Type.back}
|
||||||
|
barCodeScannerSettings={{
|
||||||
|
barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
|
||||||
|
}}
|
||||||
|
style={StyleSheet.absoluteFill}
|
||||||
|
ratio={'1:1'}
|
||||||
|
>
|
||||||
|
{this.getOverlay()}
|
||||||
|
</Camera>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{this.state.hasPermission
|
||||||
|
? this.getScanner()
|
||||||
|
: this.getPermissionScreen()
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const borderOffset = '10%';
|
||||||
|
|
||||||
|
const overlayBoxStyle = {
|
||||||
|
position: 'absolute',
|
||||||
|
width: 25,
|
||||||
|
height: 25,
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlayLineStyle = {
|
||||||
|
position: 'absolute',
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
borderRadius: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlayHorizontalLineStyle = {
|
||||||
|
...overlayLineStyle,
|
||||||
|
width: '100%',
|
||||||
|
height: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlayVerticalLineStyle = {
|
||||||
|
...overlayLineStyle,
|
||||||
|
height: '100%',
|
||||||
|
width: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlayBackground = {
|
||||||
|
backgroundColor: "rgba(0,0,0,0.47)",
|
||||||
|
position: "absolute",
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#000000' // the rock-solid workaround
|
||||||
|
},
|
||||||
|
cameraContainer: {
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
|
aspectRatio: 1,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
overlayTopLeft: {
|
||||||
|
...overlayBoxStyle,
|
||||||
|
top: borderOffset,
|
||||||
|
left: borderOffset,
|
||||||
|
},
|
||||||
|
overlayTopRight: {
|
||||||
|
...overlayBoxStyle,
|
||||||
|
top: borderOffset,
|
||||||
|
right: borderOffset,
|
||||||
|
},
|
||||||
|
overlayBottomLeft: {
|
||||||
|
...overlayBoxStyle,
|
||||||
|
bottom: borderOffset,
|
||||||
|
left: borderOffset,
|
||||||
|
},
|
||||||
|
overlayBottomRight: {
|
||||||
|
...overlayBoxStyle,
|
||||||
|
bottom: borderOffset,
|
||||||
|
right: borderOffset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withTheme(ScannerScreen);
|
|
@ -22,38 +22,42 @@ export default class URLHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
onUrl = ({url}: Object) => {
|
onUrl = ({url}: Object) => {
|
||||||
let data = this.getUrlData(Linking.parse(url));
|
let data = URLHandler.getUrlData(Linking.parse(url));
|
||||||
if (data !== null)
|
if (data !== null)
|
||||||
this.onDetectURL(data);
|
this.onDetectURL(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
onInitialUrl = ({path, queryParams}: Object) => {
|
onInitialUrl = ({path, queryParams}: Object) => {
|
||||||
let data = this.getUrlData({path, queryParams});
|
let data = URLHandler.getUrlData({path, queryParams});
|
||||||
if (data !== null)
|
if (data !== null)
|
||||||
this.onInitialURLParsed(data);
|
this.onInitialURLParsed(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
getUrlData({path, queryParams}: Object) {
|
static getUrlData({path, queryParams}: Object) {
|
||||||
let data = null;
|
let data = null;
|
||||||
if (path !== null) {
|
if (path !== null) {
|
||||||
let pathArray = path.split('/');
|
let pathArray = path.split('/');
|
||||||
if (this.isClubInformationLink(pathArray))
|
if (URLHandler.isClubInformationLink(pathArray))
|
||||||
data = this.generateClubInformationData(queryParams);
|
data = URLHandler.generateClubInformationData(queryParams);
|
||||||
else if (this.isPlanningInformationLink(pathArray))
|
else if (URLHandler.isPlanningInformationLink(pathArray))
|
||||||
data = this.generatePlanningInformationData(queryParams);
|
data = URLHandler.generatePlanningInformationData(queryParams);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
isClubInformationLink(pathArray: Array<string>) {
|
static isUrlValid(url: string) {
|
||||||
|
return this.getUrlData(Linking.parse(url)) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static isClubInformationLink(pathArray: Array<string>) {
|
||||||
return pathArray[0] === "main" && pathArray[1] === "home" && pathArray[2] === "club-information";
|
return pathArray[0] === "main" && pathArray[1] === "home" && pathArray[2] === "club-information";
|
||||||
}
|
}
|
||||||
|
|
||||||
isPlanningInformationLink(pathArray: Array<string>) {
|
static isPlanningInformationLink(pathArray: Array<string>) {
|
||||||
return pathArray[0] === "main" && pathArray[1] === "home" && pathArray[2] === "planning-information";
|
return pathArray[0] === "main" && pathArray[1] === "home" && pathArray[2] === "planning-information";
|
||||||
}
|
}
|
||||||
|
|
||||||
generateClubInformationData(params: Object): Object | null {
|
static generateClubInformationData(params: Object): Object | null {
|
||||||
if (params !== undefined && params.clubId !== undefined) {
|
if (params !== undefined && params.clubId !== undefined) {
|
||||||
let id = parseInt(params.clubId);
|
let id = parseInt(params.clubId);
|
||||||
if (!isNaN(id)) {
|
if (!isNaN(id)) {
|
||||||
|
@ -63,7 +67,7 @@ export default class URLHandler {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
generatePlanningInformationData(params: Object): Object | null {
|
static generatePlanningInformationData(params: Object): Object | null {
|
||||||
if (params !== undefined && params.eventId !== undefined) {
|
if (params !== undefined && params.eventId !== undefined) {
|
||||||
let id = parseInt(params.eventId);
|
let id = parseInt(params.eventId);
|
||||||
if (!isNaN(id)) {
|
if (!isNaN(id)) {
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
"screens": {
|
"screens": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"planning": "Planning",
|
"planning": "Planning",
|
||||||
"planningDisplayScreen": "Event Details",
|
"planningDisplayScreen": "Event details",
|
||||||
"proxiwash": "Proxiwash",
|
"proxiwash": "Proxiwash",
|
||||||
"proximo": "Proximo",
|
"proximo": "Proximo",
|
||||||
|
"proximoArticles": "Articles",
|
||||||
"menuSelf": "RU Menu",
|
"menuSelf": "RU Menu",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"availableRooms": "Available rooms",
|
"availableRooms": "Available rooms",
|
||||||
|
@ -16,7 +17,8 @@
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"vote": "Elections"
|
"vote": "Elections",
|
||||||
|
"scanner": "Scanotron 3000"
|
||||||
},
|
},
|
||||||
"sidenav": {
|
"sidenav": {
|
||||||
"divider1": "Student websites",
|
"divider1": "Student websites",
|
||||||
|
@ -224,6 +226,10 @@
|
||||||
"membershipPayed": "Payed",
|
"membershipPayed": "Payed",
|
||||||
"membershipNotPayed": "Not payed"
|
"membershipNotPayed": "Not payed"
|
||||||
},
|
},
|
||||||
|
"scannerScreen": {
|
||||||
|
"errorTitle": "QR code invalid",
|
||||||
|
"errorMessage": "The QR code scanned could not be recognised, please make sure it is valid."
|
||||||
|
},
|
||||||
"loginScreen": {
|
"loginScreen": {
|
||||||
"title": "Amicale account",
|
"title": "Amicale account",
|
||||||
"subtitle": "Please enter your credentials",
|
"subtitle": "Please enter your credentials",
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"planningDisplayScreen": "Détails",
|
"planningDisplayScreen": "Détails",
|
||||||
"proxiwash": "Proxiwash",
|
"proxiwash": "Proxiwash",
|
||||||
"proximo": "Proximo",
|
"proximo": "Proximo",
|
||||||
|
"proximoArticles": "Articles",
|
||||||
"menuSelf": "Menu du RU",
|
"menuSelf": "Menu du RU",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"availableRooms": "Salles dispo",
|
"availableRooms": "Salles dispo",
|
||||||
|
@ -16,7 +17,8 @@
|
||||||
"login": "Se Connecter",
|
"login": "Se Connecter",
|
||||||
"logout": "Se Déconnecter",
|
"logout": "Se Déconnecter",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"vote": "Élections"
|
"vote": "Élections",
|
||||||
|
"scanner": "Scanotron 3000"
|
||||||
},
|
},
|
||||||
"sidenav": {
|
"sidenav": {
|
||||||
"divider1": "Sites étudiants",
|
"divider1": "Sites étudiants",
|
||||||
|
@ -224,6 +226,10 @@
|
||||||
"membershipPayed": "Payée",
|
"membershipPayed": "Payée",
|
||||||
"membershipNotPayed": "Non payée"
|
"membershipNotPayed": "Non payée"
|
||||||
},
|
},
|
||||||
|
"scannerScreen": {
|
||||||
|
"errorTitle": "QR code invalide",
|
||||||
|
"errorMessage": "Le QR code scannée n'a pas été reconnu. Merci de vérifier sa validité."
|
||||||
|
},
|
||||||
"loginScreen": {
|
"loginScreen": {
|
||||||
"title": "Compte Amicale",
|
"title": "Compte Amicale",
|
||||||
"subtitle": "Entrez vos identifiants",
|
"subtitle": "Entrez vos identifiants",
|
||||||
|
|
Loading…
Reference in a new issue