Application Android et IOS pour l'amicale des élèves
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ScannerScreen.js 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. // @flow
  2. import * as React from 'react';
  3. import {Linking, Platform, StyleSheet, View} from "react-native";
  4. import {Button, Text, withTheme} from 'react-native-paper';
  5. import {RNCamera} from 'react-native-camera';
  6. import {BarcodeMask} from '@nartc/react-native-barcode-mask';
  7. import URLHandler from "../../utils/URLHandler";
  8. import AlertDialog from "../../components/Dialogs/AlertDialog";
  9. import i18n from 'i18n-js';
  10. import CustomTabBar from "../../components/Tabbar/CustomTabBar";
  11. import LoadingConfirmDialog from "../../components/Dialogs/LoadingConfirmDialog";
  12. import {PERMISSIONS, request, RESULTS} from 'react-native-permissions';
  13. import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
  14. import MascotPopup from "../../components/Mascot/MascotPopup";
  15. type Props = {};
  16. type State = {
  17. hasPermission: boolean,
  18. scanned: boolean,
  19. dialogVisible: boolean,
  20. mascotDialogVisible: boolean,
  21. dialogTitle: string,
  22. dialogMessage: string,
  23. loading: boolean,
  24. };
  25. class ScannerScreen extends React.Component<Props, State> {
  26. state = {
  27. hasPermission: false,
  28. scanned: false,
  29. mascotDialogVisible: false,
  30. dialogVisible: false,
  31. dialogTitle: "",
  32. dialogMessage: "",
  33. loading: false,
  34. };
  35. constructor() {
  36. super();
  37. }
  38. componentDidMount() {
  39. this.requestPermissions();
  40. }
  41. /**
  42. * Requests permission to use the camera
  43. */
  44. requestPermissions = () => {
  45. if (Platform.OS === 'android')
  46. request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus)
  47. else
  48. request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus)
  49. };
  50. /**
  51. * Updates the state permission status
  52. *
  53. * @param result
  54. */
  55. updatePermissionStatus = (result) => this.setState({hasPermission: result === RESULTS.GRANTED});
  56. /**
  57. * Opens scanned link if it is a valid app link or shows and error dialog
  58. *
  59. * @param type The barcode type
  60. * @param data The scanned value
  61. */
  62. handleCodeScanned = ({type, data}) => {
  63. if (!URLHandler.isUrlValid(data))
  64. this.showErrorDialog();
  65. else {
  66. this.showOpeningDialog();
  67. Linking.openURL(data);
  68. }
  69. };
  70. /**
  71. * Gets a view asking user for permission to use the camera
  72. *
  73. * @returns {*}
  74. */
  75. getPermissionScreen() {
  76. return <View style={{marginLeft: 10, marginRight: 10}}>
  77. <Text>{i18n.t("screens.scanner.permissions.error")}</Text>
  78. <Button
  79. icon="camera"
  80. mode="contained"
  81. onPress={this.requestPermissions}
  82. style={{
  83. marginTop: 10,
  84. marginLeft: 'auto',
  85. marginRight: 'auto',
  86. }}
  87. >
  88. {i18n.t("screens.scanner.permissions.button")}
  89. </Button>
  90. </View>
  91. }
  92. /**
  93. * Shows a dialog indicating how to use the scanner
  94. */
  95. showHelpDialog = () => {
  96. this.setState({
  97. mascotDialogVisible: true,
  98. scanned: true,
  99. });
  100. };
  101. /**
  102. * Shows a loading dialog
  103. */
  104. showOpeningDialog = () => {
  105. this.setState({
  106. loading: true,
  107. scanned: true,
  108. });
  109. };
  110. /**
  111. * Shows a dialog indicating the user the scanned code was invalid
  112. */
  113. showErrorDialog() {
  114. this.setState({
  115. dialogVisible: true,
  116. scanned: true,
  117. });
  118. }
  119. /**
  120. * Hide any dialog
  121. */
  122. onDialogDismiss = () => this.setState({
  123. dialogVisible: false,
  124. scanned: false,
  125. });
  126. onMascotDialogDismiss = () => this.setState({
  127. mascotDialogVisible: false,
  128. scanned: false,
  129. });
  130. /**
  131. * Gets a view with the scanner.
  132. * This scanner uses the back camera, can only scan qr codes and has a square mask on the center.
  133. * The mask is only for design purposes as a code is scanned as soon as it enters the camera view
  134. *
  135. * @returns {*}
  136. */
  137. getScanner() {
  138. return (
  139. <RNCamera
  140. onBarCodeRead={this.state.scanned ? undefined : this.handleCodeScanned}
  141. type={RNCamera.Constants.Type.back}
  142. barCodeScannerSettings={{
  143. barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
  144. }}
  145. style={StyleSheet.absoluteFill}
  146. captureAudio={false}
  147. >
  148. <BarcodeMask
  149. backgroundColor={"#000"}
  150. maskOpacity={0.5}
  151. animatedLineThickness={1}
  152. animationDuration={1000}
  153. width={250}
  154. height={250}
  155. />
  156. </RNCamera>
  157. );
  158. }
  159. render() {
  160. return (
  161. <View style={{
  162. ...styles.container,
  163. marginBottom: CustomTabBar.TAB_BAR_HEIGHT
  164. }}>
  165. {this.state.hasPermission
  166. ? this.getScanner()
  167. : this.getPermissionScreen()
  168. }
  169. <Button
  170. icon="information"
  171. mode="contained"
  172. onPress={this.showHelpDialog}
  173. style={styles.button}
  174. >
  175. {i18n.t("screens.scanner.help.button")}
  176. </Button>
  177. <MascotPopup
  178. visible={this.state.mascotDialogVisible}
  179. title={i18n.t("screens.scanner.mascotDialog.title")}
  180. message={i18n.t("screens.scanner.mascotDialog.message")}
  181. icon={"camera-iris"}
  182. buttons={{
  183. action: null,
  184. cancel: {
  185. message: i18n.t("screens.scanner.mascotDialog.button"),
  186. icon: "check",
  187. onPress: this.onMascotDialogDismiss,
  188. }
  189. }}
  190. emotion={MASCOT_STYLE.NORMAL}
  191. />
  192. <AlertDialog
  193. visible={this.state.dialogVisible}
  194. onDismiss={this.onDialogDismiss}
  195. title={i18n.t("screens.scanner.error.title")}
  196. message={i18n.t("screens.scanner.error.message")}
  197. />
  198. <LoadingConfirmDialog
  199. visible={this.state.loading}
  200. titleLoading={i18n.t("general.loading")}
  201. startLoading={true}
  202. />
  203. </View>
  204. );
  205. }
  206. }
  207. const styles = StyleSheet.create({
  208. container: {
  209. flex: 1,
  210. justifyContent: 'center',
  211. },
  212. button: {
  213. position: 'absolute',
  214. bottom: 20,
  215. width: '80%',
  216. left: '10%'
  217. },
  218. });
  219. export default withTheme(ScannerScreen);