diff --git a/package.json b/package.json
index c6c99ab..1a15199 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,9 @@
"react-native-render-html": "^4.1.2",
"react-native-safe-area-context": "0.7.3",
"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": {
"@babel/cli": "^7.8.4",
diff --git a/src/navigation/MainTabNavigator.js b/src/navigation/MainTabNavigator.js
index 041e7a9..51a2735 100644
--- a/src/navigation/MainTabNavigator.js
+++ b/src/navigation/MainTabNavigator.js
@@ -17,6 +17,7 @@ import HeaderButton from "../components/Custom/HeaderButton";
import {withTheme} from 'react-native-paper';
import i18n from "i18n-js";
import ClubDisplayScreen from "../screens/Amicale/Clubs/ClubDisplayScreen";
+import ScannerScreen from "../screens/ScannerScreen";
const TAB_ICONS = {
@@ -62,7 +63,7 @@ function ProximoStackComponent() {
@@ -70,7 +71,7 @@ function ProximoStackComponent() {
name="proximo-about"
component={ProximoAboutScreen}
options={{
- title: 'Proximo',
+ title: i18n.t('screens.proximo'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
@@ -93,7 +94,7 @@ function ProxiwashStackComponent() {
options={({navigation}) => {
const openDrawer = getDrawerButton.bind(this, navigation);
return {
- title: 'Proxiwash',
+ title: i18n.t('screens.proxiwash'),
headerLeft: openDrawer
};
}}
@@ -102,7 +103,7 @@ function ProxiwashStackComponent() {
name="proxiwash-about"
component={ProxiwashAboutScreen}
options={{
- title: 'Proxiwash',
+ title: i18n.t('screens.proxiwash'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
@@ -125,7 +126,7 @@ function PlanningStackComponent() {
options={({navigation}) => {
const openDrawer = getDrawerButton.bind(this, navigation);
return {
- title: 'Planning',
+ title: i18n.t('screens.planning'),
headerLeft: openDrawer
};
}}
@@ -134,7 +135,7 @@ function PlanningStackComponent() {
name="planning-information"
component={PlanningDisplayScreen}
options={{
- title: 'Details',
+ title: i18n.t('screens.planningDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
@@ -171,7 +172,7 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
name="planning-information"
component={PlanningDisplayScreen}
options={{
- title: 'Details',
+ title: i18n.t('screens.planningDisplayScreen'),
...TransitionPresets.ModalSlideFromBottomIOS,
}}
/>
@@ -180,7 +181,17 @@ function HomeStackComponent(initialRoute: string | null, defaultData: Object) {
component={ClubDisplayScreen}
options={({navigation}) => {
return {
- title: "",
+ title: '',
+ ...TransitionPresets.ModalSlideFromBottomIOS,
+ };
+ }}
+ />
+ {
+ return {
+ title: i18n.t('screens.scanner'),
...TransitionPresets.ModalSlideFromBottomIOS,
};
}}
diff --git a/src/screens/HomeScreen.js b/src/screens/HomeScreen.js
index 30bf86c..f753ab9 100644
--- a/src/screens/HomeScreen.js
+++ b/src/screens/HomeScreen.js
@@ -1,11 +1,11 @@
// @flow
import * as React from 'react';
-import {View} from 'react-native';
+import {StyleSheet, View} from 'react-native';
import i18n from "i18n-js";
import DashboardItem from "../components/Home/EventDashboardItem";
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 SquareDashboardItem from "../components/Home/SmallDashboardItem";
import PreviewEventDashboardItem from "../components/Home/PreviewEventDashboardItem";
@@ -467,9 +467,12 @@ class HomeScreen extends React.Component {
this.getDashboardItem(item) : this.getFeedItem(item));
}
+ openScanner = () => this.props.navigation.navigate("scanner");
+
render() {
const nav = this.props.navigation;
return (
+
{
refreshOnFocus={true}
fetchUrl={DATA_URL}
renderItem={this.getRenderItem}/>
+
+
);
}
}
+const styles = StyleSheet.create({
+ fab: {
+ position: 'absolute',
+ margin: 16,
+ right: 0,
+ bottom: 0,
+ },
+});
+
export default withTheme(HomeScreen);
diff --git a/src/screens/ScannerScreen.js b/src/screens/ScannerScreen.js
new file mode 100644
index 0000000..b46d6dd
--- /dev/null
+++ b/src/screens/ScannerScreen.js
@@ -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 {
+
+ 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 PLS
+ }
+
+ getOverlay() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ showErrorDialog() {
+ this.setState({dialogVisible: true});
+ }
+
+ onDialogDismiss = () => this.setState({
+ dialogVisible: false,
+ scanned: false,
+ });
+
+ getScanner() {
+ return (
+
+
+
+ {this.getOverlay()}
+
+
+ );
+ }
+
+ render() {
+ return (
+
+ {this.state.hasPermission
+ ? this.getScanner()
+ : this.getPermissionScreen()
+ }
+
+ );
+ }
+}
+
+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);
diff --git a/src/utils/URLHandler.js b/src/utils/URLHandler.js
index ae9f41b..b907057 100644
--- a/src/utils/URLHandler.js
+++ b/src/utils/URLHandler.js
@@ -22,38 +22,42 @@ export default class URLHandler {
}
onUrl = ({url}: Object) => {
- let data = this.getUrlData(Linking.parse(url));
+ let data = URLHandler.getUrlData(Linking.parse(url));
if (data !== null)
this.onDetectURL(data);
};
onInitialUrl = ({path, queryParams}: Object) => {
- let data = this.getUrlData({path, queryParams});
+ let data = URLHandler.getUrlData({path, queryParams});
if (data !== null)
this.onInitialURLParsed(data);
};
- getUrlData({path, queryParams}: Object) {
+ static getUrlData({path, queryParams}: Object) {
let data = null;
if (path !== null) {
let pathArray = path.split('/');
- if (this.isClubInformationLink(pathArray))
- data = this.generateClubInformationData(queryParams);
- else if (this.isPlanningInformationLink(pathArray))
- data = this.generatePlanningInformationData(queryParams);
+ if (URLHandler.isClubInformationLink(pathArray))
+ data = URLHandler.generateClubInformationData(queryParams);
+ else if (URLHandler.isPlanningInformationLink(pathArray))
+ data = URLHandler.generatePlanningInformationData(queryParams);
}
return data;
}
- isClubInformationLink(pathArray: Array) {
+ static isUrlValid(url: string) {
+ return this.getUrlData(Linking.parse(url)) !== null;
+ }
+
+ static isClubInformationLink(pathArray: Array) {
return pathArray[0] === "main" && pathArray[1] === "home" && pathArray[2] === "club-information";
}
- isPlanningInformationLink(pathArray: Array) {
+ static isPlanningInformationLink(pathArray: Array) {
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) {
let id = parseInt(params.clubId);
if (!isNaN(id)) {
@@ -63,7 +67,7 @@ export default class URLHandler {
return null;
}
- generatePlanningInformationData(params: Object): Object | null {
+ static generatePlanningInformationData(params: Object): Object | null {
if (params !== undefined && params.eventId !== undefined) {
let id = parseInt(params.eventId);
if (!isNaN(id)) {
diff --git a/translations/en.json b/translations/en.json
index 6e1658c..5948d81 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -2,9 +2,10 @@
"screens": {
"home": "Home",
"planning": "Planning",
- "planningDisplayScreen": "Event Details",
+ "planningDisplayScreen": "Event details",
"proxiwash": "Proxiwash",
"proximo": "Proximo",
+ "proximoArticles": "Articles",
"menuSelf": "RU Menu",
"settings": "Settings",
"availableRooms": "Available rooms",
@@ -16,7 +17,8 @@
"login": "Login",
"logout": "Logout",
"profile": "Profile",
- "vote": "Elections"
+ "vote": "Elections",
+ "scanner": "Scanotron 3000"
},
"sidenav": {
"divider1": "Student websites",
@@ -224,6 +226,10 @@
"membershipPayed": "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": {
"title": "Amicale account",
"subtitle": "Please enter your credentials",
diff --git a/translations/fr.json b/translations/fr.json
index 0834878..96e5f6d 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -5,6 +5,7 @@
"planningDisplayScreen": "Détails",
"proxiwash": "Proxiwash",
"proximo": "Proximo",
+ "proximoArticles": "Articles",
"menuSelf": "Menu du RU",
"settings": "Paramètres",
"availableRooms": "Salles dispo",
@@ -16,7 +17,8 @@
"login": "Se Connecter",
"logout": "Se Déconnecter",
"profile": "Profil",
- "vote": "Élections"
+ "vote": "Élections",
+ "scanner": "Scanotron 3000"
},
"sidenav": {
"divider1": "Sites étudiants",
@@ -224,6 +226,10 @@
"membershipPayed": "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": {
"title": "Compte Amicale",
"subtitle": "Entrez vos identifiants",