scanQrPage.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { CameraView, useCameraPermissions } from 'expo-camera';
  2. import { useEffect, useRef, useState } from 'react';
  3. import { ActivityIndicator, Dimensions, Pressable, ScrollView, StyleSheet, Text, Vibration, View } from 'react-native';
  4. import ChooseCarForChargingRow from '../../../../component/global/chooseCarForChargingRow';
  5. import { CrossLogoWhiteSvg, QuestionSvg } from '../../../../component/global/SVG';
  6. import { router } from 'expo-router';
  7. import { chargeStationService } from '../../../../service/chargeStationService';
  8. const ScanQrPage = () => {
  9. const [permission, requestPermission] = useCameraPermissions();
  10. const [scanned, setScanned] = useState(false);
  11. const viewRef = useRef(null);
  12. const [scannedResult, setScannedResult] = useState('');
  13. useEffect(() => {
  14. (async () => {
  15. const { status } = await requestPermission();
  16. if (status !== 'granted') {
  17. alert('需要相機權限以掃描QR碼');
  18. }
  19. })();
  20. }, []);
  21. if (!permission) {
  22. return <View />;
  23. }
  24. if (!permission.granted) {
  25. return (
  26. <View className="flex-1 justify-center items-center">
  27. <Text style={{ textAlign: 'center' }}>需要相機權限以掃描QR碼,請在設定中開啟相機權限</Text>
  28. </View>
  29. );
  30. }
  31. const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
  32. const handleBarCodeScanned = ({ bounds, data, type }: { bounds: any; data: any; type: any }) => {
  33. const { origin, size } = bounds;
  34. // Calculate the size of the square transparent area
  35. const transparentAreaSize = Math.min(screenWidth * 0.6, screenHeight * 0.3);
  36. const transparentAreaX = (screenWidth - transparentAreaSize) / 2;
  37. const transparentAreaY = (screenHeight - transparentAreaSize) / 2;
  38. // Check if the barcode is within the transparent area
  39. if (
  40. origin.x >= transparentAreaX &&
  41. origin.y >= transparentAreaY &&
  42. origin.x + size.width <= transparentAreaX + transparentAreaSize &&
  43. origin.y + size.height <= transparentAreaY + transparentAreaSize
  44. ) {
  45. setScanned(true);
  46. setScannedResult(data);
  47. Vibration.vibrate(100);
  48. console.log(` type: ${type} data: ${data} `);
  49. startCharging(data);
  50. setTimeout(() => {
  51. setScanned(false);
  52. }, 5000);
  53. }
  54. };
  55. // const dummyDataChooseCarForCharging = [
  56. // {
  57. // imageUrl: require('../../../../assets/car1.png'),
  58. // VehicleName: 'TESLA - Model 3',
  59. // isDefault: true
  60. // },
  61. // { VehicleName: 'TESLA - Model Y', isDefault: false },
  62. // {
  63. // imageUrl: require('../../../../assets/car1.png'),
  64. // VehicleName: 'TESLA - Model X',
  65. // isDefault: false
  66. // },
  67. // { VehicleName: 'TESLA - Model 3', isDefault: false }
  68. // ];
  69. const ChooseCar = () => {
  70. const [loading, setLoading] = useState(false);
  71. const isLargeScreen = screenHeight >= 800;
  72. const [carData, setCarData] = useState([]);
  73. const defaultImageUrl = require('../../../../assets/car1.png');
  74. const [selectedCar, setSelectedCar] = useState('');
  75. useEffect(() => {
  76. const fetchAllCars = async () => {
  77. setLoading(true);
  78. try {
  79. const response = await chargeStationService.getUserCars();
  80. if (response) {
  81. // console.log(response.data);
  82. const carTypes = response.data.map((item: any) => ({
  83. id: item.id,
  84. name: item.car_type.name,
  85. image: item.car_type.type_image_url
  86. }));
  87. // console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', carTypes);
  88. let updatedCarTypes = [...carTypes];
  89. for (let i = 0; i < carTypes.length; i++) {
  90. const car = updatedCarTypes[i];
  91. const imageUrl = await chargeStationService.getProcessedImageUrl(car.image);
  92. updatedCarTypes[i] = {
  93. ...car,
  94. image: imageUrl
  95. };
  96. }
  97. setCarData(updatedCarTypes);
  98. return true;
  99. }
  100. } catch (error) {
  101. console.log(error);
  102. } finally {
  103. setLoading(false);
  104. }
  105. };
  106. fetchAllCars();
  107. }, []);
  108. useEffect(() => {
  109. console.log('Current carData:', carData);
  110. }, [carData]);
  111. // useEffect(() => {
  112. // console.log('Current selectedCar:', selectedCar);
  113. // }, [selectedCar]);
  114. return (
  115. <View
  116. style={{
  117. ...(isLargeScreen
  118. ? {
  119. marginTop: '10%',
  120. marginBottom: '12%',
  121. paddingBottom: 12
  122. }
  123. : {
  124. flex: 1,
  125. alignItems: 'center',
  126. justifyContent: 'center'
  127. })
  128. }}
  129. >
  130. <View className="justify-center items-center flex-1 ">
  131. <View
  132. style={{
  133. ...(isLargeScreen
  134. ? {}
  135. : {
  136. backgroundColor: 'rgba(0,0,0,0.7)'
  137. })
  138. }}
  139. >
  140. {loading ? (
  141. <View className="w-full">
  142. <ActivityIndicator color="#34657b" />
  143. </View>
  144. ) : (
  145. <View className="w-full bg-[#000000B3]">
  146. <View className="flex-row items-center justify-between mx-[5%] ">
  147. <Pressable
  148. className="pt-4 "
  149. onPress={() => {
  150. if (router.canGoBack()) {
  151. router.back();
  152. } else {
  153. router.replace('mainPage');
  154. }
  155. }}
  156. >
  157. <CrossLogoWhiteSvg />
  158. </Pressable>
  159. <Text className="text-base text-white pt-2">選擇充電車輛</Text>
  160. <Text className="text-xl text-white pt-2"></Text>
  161. </View>
  162. <ScrollView
  163. horizontal={true}
  164. showsHorizontalScrollIndicator={false}
  165. contentContainerStyle={{
  166. alignItems: 'center',
  167. flexDirection: 'row',
  168. marginVertical: 12
  169. }}
  170. className="space-x-2 mx-[5%]"
  171. >
  172. {carData.map((car, index) => (
  173. <ChooseCarForChargingRow
  174. key={`${car.name}+${index}`}
  175. image={car.image}
  176. onPress={() => setSelectedCar(car.id)}
  177. isSelected={selectedCar === car.id}
  178. // imageUrl={image}
  179. VehicleName={car.name}
  180. isDefault={car.isDefault}
  181. />
  182. ))}
  183. </ScrollView>
  184. </View>
  185. )}
  186. </View>
  187. </View>
  188. </View>
  189. );
  190. };
  191. //scan==> retrieve string ==> PUT charge/start requrest ==>
  192. const startCharging = async (scannedResult: string) => {
  193. try {
  194. const requestToBeSent = {
  195. StartChargeSeq: 'what is this',
  196. ConnectorID: scannedResult,
  197. StopBy: 'what is this',
  198. StopValue: 'what is this',
  199. StartBalance: 'what is this'
  200. };
  201. const response = await chargeStationService.startCharging(requestToBeSent);
  202. console.log('Charging started', response);
  203. // ADD NAVIGATE TO NEW PAGE HERE
  204. } catch (error) {
  205. console.log('Failed to start charging:', error);
  206. }
  207. };
  208. return (
  209. <View style={styles.container} ref={viewRef}>
  210. <CameraView
  211. style={styles.camera}
  212. facing="back"
  213. barcodeScannerSettings={{
  214. barcodeTypes: ['qr']
  215. }}
  216. onBarcodeScanned={scanned ? undefined : handleBarCodeScanned}
  217. responsiveOrientationWhenOrientationLocked={true}
  218. >
  219. <View style={styles.overlay}>
  220. <View style={styles.topOverlay}>
  221. <ChooseCar />
  222. </View>
  223. <View style={styles.centerRow}>
  224. <View style={styles.leftOverlay}></View>
  225. <View style={styles.transparentArea}></View>
  226. <View style={styles.rightOverlay} />
  227. </View>
  228. <View className="items-center justify-between" style={styles.bottomOverlay}>
  229. <View>
  230. <Text className="text-white text-lg font-bold mt-2 ">請掃瞄充電座上的二維碼</Text>
  231. </View>
  232. <View className="flex-row space-x-2 items-center ">
  233. <QuestionSvg />
  234. <Pressable onPress={() => router.push('assistancePage')}>
  235. <Text className="text-white text-base">需要協助?</Text>
  236. </Pressable>
  237. </View>
  238. <View />
  239. </View>
  240. </View>
  241. </CameraView>
  242. </View>
  243. );
  244. };
  245. const styles = StyleSheet.create({
  246. container: {
  247. flex: 1
  248. },
  249. camera: {
  250. flex: 1
  251. },
  252. overlay: {
  253. flex: 1
  254. },
  255. topOverlay: {
  256. flex: 35,
  257. alignItems: 'center',
  258. backgroundColor: 'rgba(0,0,0,0.5)'
  259. },
  260. centerRow: {
  261. flex: 30,
  262. flexDirection: 'row'
  263. },
  264. leftOverlay: {
  265. flex: 20,
  266. backgroundColor: 'rgba(0,0,0,0.5)'
  267. },
  268. transparentArea: {
  269. flex: 60,
  270. aspectRatio: 1,
  271. position: 'relative'
  272. },
  273. rightOverlay: {
  274. flex: 20,
  275. backgroundColor: 'rgba(0,0,0,0.5)'
  276. },
  277. bottomOverlay: {
  278. flex: 35,
  279. backgroundColor: 'rgba(0,0,0,0.5)'
  280. }
  281. });
  282. export default ScanQrPage;