Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
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.tsx 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import * as React from 'react';
  20. import { Linking, Platform, StyleSheet, View } from 'react-native';
  21. import { Button, Text } from 'react-native-paper';
  22. import { BarCodeReadEvent, RNCamera } from 'react-native-camera';
  23. import { BarcodeMask } from '@nartc/react-native-barcode-mask';
  24. import i18n from 'i18n-js';
  25. import { PERMISSIONS, request, RESULTS } from 'react-native-permissions';
  26. import URLHandler from '../../utils/URLHandler';
  27. import AlertDialog from '../../components/Dialogs/AlertDialog';
  28. import { TAB_BAR_HEIGHT } from '../../components/Tabbar/CustomTabBar';
  29. import LoadingConfirmDialog from '../../components/Dialogs/LoadingConfirmDialog';
  30. import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
  31. import MascotPopup from '../../components/Mascot/MascotPopup';
  32. type StateType = {
  33. hasPermission: boolean;
  34. scanned: boolean;
  35. dialogVisible: boolean;
  36. mascotDialogVisible: boolean;
  37. loading: boolean;
  38. };
  39. const styles = StyleSheet.create({
  40. container: {
  41. flex: 1,
  42. justifyContent: 'center',
  43. },
  44. button: {
  45. position: 'absolute',
  46. bottom: 20,
  47. width: '80%',
  48. left: '10%',
  49. },
  50. permissionContainer: {
  51. marginLeft: 10,
  52. marginRight: 10,
  53. },
  54. permissionButton: {
  55. marginTop: 10,
  56. marginLeft: 'auto',
  57. marginRight: 'auto',
  58. },
  59. });
  60. type PermissionResults = 'unavailable' | 'denied' | 'blocked' | 'granted';
  61. class ScannerScreen extends React.Component<{}, StateType> {
  62. constructor(props: {}) {
  63. super(props);
  64. this.state = {
  65. hasPermission: false,
  66. scanned: false,
  67. mascotDialogVisible: false,
  68. dialogVisible: false,
  69. loading: false,
  70. };
  71. }
  72. componentDidMount() {
  73. this.requestPermissions();
  74. }
  75. /**
  76. * Gets a view asking user for permission to use the camera
  77. *
  78. * @returns {*}
  79. */
  80. getPermissionScreen() {
  81. return (
  82. <View style={styles.permissionContainer}>
  83. <Text>{i18n.t('screens.scanner.permissions.error')}</Text>
  84. <Button
  85. icon="camera"
  86. mode="contained"
  87. onPress={this.requestPermissions}
  88. style={styles.permissionButton}
  89. >
  90. {i18n.t('screens.scanner.permissions.button')}
  91. </Button>
  92. </View>
  93. );
  94. }
  95. /**
  96. * Gets a view with the scanner.
  97. * This scanner uses the back camera, can only scan qr codes and has a square mask on the center.
  98. * The mask is only for design purposes as a code is scanned as soon as it enters the camera view
  99. *
  100. * @returns {*}
  101. */
  102. getScanner() {
  103. const { state } = this;
  104. return (
  105. <RNCamera
  106. onBarCodeRead={state.scanned ? undefined : this.onCodeScanned}
  107. type={RNCamera.Constants.Type.back}
  108. barCodeTypes={[RNCamera.Constants.BarCodeType.qr]}
  109. style={StyleSheet.absoluteFill}
  110. captureAudio={false}
  111. >
  112. <BarcodeMask
  113. backgroundColor="#000"
  114. maskOpacity={0.5}
  115. animatedLineThickness={1}
  116. animationDuration={1000}
  117. width={250}
  118. height={250}
  119. />
  120. </RNCamera>
  121. );
  122. }
  123. /**
  124. * Requests permission to use the camera
  125. */
  126. requestPermissions = () => {
  127. if (Platform.OS === 'android') {
  128. request(PERMISSIONS.ANDROID.CAMERA).then(this.updatePermissionStatus);
  129. } else {
  130. request(PERMISSIONS.IOS.CAMERA).then(this.updatePermissionStatus);
  131. }
  132. };
  133. /**
  134. * Updates the state permission status
  135. *
  136. * @param result
  137. */
  138. updatePermissionStatus = (result: PermissionResults) => {
  139. this.setState({
  140. hasPermission: result === RESULTS.GRANTED,
  141. });
  142. };
  143. /**
  144. * Shows a dialog indicating the user the scanned code was invalid
  145. */
  146. showErrorDialog() {
  147. this.setState({
  148. dialogVisible: true,
  149. scanned: true,
  150. });
  151. }
  152. /**
  153. * Shows a dialog indicating how to use the scanner
  154. */
  155. showHelpDialog = () => {
  156. this.setState({
  157. mascotDialogVisible: true,
  158. scanned: true,
  159. });
  160. };
  161. /**
  162. * Shows a loading dialog
  163. */
  164. showOpeningDialog = () => {
  165. this.setState({
  166. loading: true,
  167. scanned: true,
  168. });
  169. };
  170. /**
  171. * Hide any dialog
  172. */
  173. onDialogDismiss = () => {
  174. this.setState({
  175. dialogVisible: false,
  176. scanned: false,
  177. });
  178. };
  179. onMascotDialogDismiss = () => {
  180. this.setState({
  181. mascotDialogVisible: false,
  182. scanned: false,
  183. });
  184. };
  185. /**
  186. * Opens scanned link if it is a valid app link or shows and error dialog
  187. *
  188. * @param event
  189. */
  190. onCodeScanned = (event: BarCodeReadEvent) => {
  191. if (!URLHandler.isUrlValid(event.data)) {
  192. this.showErrorDialog();
  193. } else {
  194. this.showOpeningDialog();
  195. Linking.openURL(event.data);
  196. }
  197. };
  198. render() {
  199. const { state } = this;
  200. return (
  201. <View
  202. style={{
  203. ...styles.container,
  204. marginBottom: TAB_BAR_HEIGHT,
  205. }}
  206. >
  207. {state.hasPermission ? this.getScanner() : this.getPermissionScreen()}
  208. <Button
  209. icon="information"
  210. mode="contained"
  211. onPress={this.showHelpDialog}
  212. style={styles.button}
  213. >
  214. {i18n.t('screens.scanner.help.button')}
  215. </Button>
  216. <MascotPopup
  217. visible={state.mascotDialogVisible}
  218. title={i18n.t('screens.scanner.mascotDialog.title')}
  219. message={i18n.t('screens.scanner.mascotDialog.message')}
  220. icon="camera-iris"
  221. buttons={{
  222. cancel: {
  223. message: i18n.t('screens.scanner.mascotDialog.button'),
  224. icon: 'check',
  225. onPress: this.onMascotDialogDismiss,
  226. },
  227. }}
  228. emotion={MASCOT_STYLE.NORMAL}
  229. />
  230. <AlertDialog
  231. visible={state.dialogVisible}
  232. onDismiss={this.onDialogDismiss}
  233. title={i18n.t('screens.scanner.error.title')}
  234. message={i18n.t('screens.scanner.error.message')}
  235. />
  236. <LoadingConfirmDialog
  237. visible={state.loading}
  238. titleLoading={i18n.t('general.loading')}
  239. startLoading
  240. />
  241. </View>
  242. );
  243. }
  244. }
  245. export default ScannerScreen;