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-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",
|
||||
|
|
|
@ -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() {
|
|||
<ProximoStack.Screen
|
||||
name="proximo-list"
|
||||
options={{
|
||||
title: 'Articles'
|
||||
title: i18n.t('screens.proximoArticles')
|
||||
}}
|
||||
component={ProximoListScreen}
|
||||
/>
|
||||
|
@ -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,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
<HomeStack.Screen
|
||||
name="scanner"
|
||||
component={ScannerScreen}
|
||||
options={({navigation}) => {
|
||||
return {
|
||||
title: i18n.t('screens.scanner'),
|
||||
...TransitionPresets.ModalSlideFromBottomIOS,
|
||||
};
|
||||
}}
|
||||
|
|
|
@ -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<Props> {
|
|||
this.getDashboardItem(item) : this.getFeedItem(item));
|
||||
}
|
||||
|
||||
openScanner = () => this.props.navigation.navigate("scanner");
|
||||
|
||||
render() {
|
||||
const nav = this.props.navigation;
|
||||
return (
|
||||
<View>
|
||||
<WebSectionList
|
||||
createDataset={this.createDataset}
|
||||
navigation={nav}
|
||||
|
@ -477,8 +480,23 @@ class HomeScreen extends React.Component<Props> {
|
|||
refreshOnFocus={true}
|
||||
fetchUrl={DATA_URL}
|
||||
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);
|
||||
|
|
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) => {
|
||||
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<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";
|
||||
}
|
||||
|
||||
isPlanningInformationLink(pathArray: Array<string>) {
|
||||
static isPlanningInformationLink(pathArray: Array<string>) {
|
||||
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)) {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue