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.tsx 6.5KB

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