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

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