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

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