hello.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import { CameraView, useCameraPermissions } from 'expo-camera';
  2. import { useEffect, useRef, useState } from 'react';
  3. import {
  4. ActivityIndicator,
  5. Alert,
  6. Dimensions,
  7. Pressable,
  8. ScrollView,
  9. StyleSheet,
  10. Text,
  11. Vibration,
  12. View
  13. } from 'react-native';
  14. import ChooseCarForChargingRow from '../../../../component/global/chooseCarForChargingRow';
  15. import { CrossLogoWhiteSvg, QuestionSvg } from '../../../../component/global/SVG';
  16. import { router } from 'expo-router';
  17. import { chargeStationService } from '../../../../service/chargeStationService';
  18. import { authenticationService } from '../../../../service/authService';
  19. import { walletService } from '../../../../service/walletService';
  20. import useUserInfoStore from '../../../../providers/userinfo_store';
  21. const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
  22. const ChooseCar = ({ carData, loading, selectedCar, setSelectedCar }) => {
  23. const isLargeScreen = screenHeight >= 800;
  24. const defaultImageUrl = require('../../../../assets/car1.png');
  25. return (
  26. <View style={{ ...(isLargeScreen ? { marginTop: '10%' , marginBottom: '12%' , paddingBottom: 12 } : { flex: 1,
  27. alignItems: 'center' , justifyContent: 'center' }) }}>
  28. <View className="justify-center items-center flex-1 ">
  29. <View style={{ ...(isLargeScreen ? {} : { backgroundColor: 'rgba(0,0,0,0.7)' }) }}>
  30. {loading ? (
  31. <View className="w-full">
  32. <ActivityIndicator color="#34657b" />
  33. </View>
  34. ) : (
  35. <View className="w-screen bg-[#000000B3]">
  36. <View className="flex-row items-center justify-between mx-[5%] ">
  37. <Pressable className="pt-4 " onPress={()=> {
  38. if (router.canGoBack()) {
  39. router.back();
  40. } else {
  41. router.replace('mainPage');
  42. }
  43. }}
  44. >
  45. <CrossLogoWhiteSvg />
  46. </Pressable>
  47. <Text className="text-base text-white pt-2">選擇充電車輛</Text>
  48. <Text className="text-xl text-white pt-2"></Text>
  49. </View>
  50. <ScrollView horizontal={true} showsHorizontalScrollIndicator={false} contentContainerStyle={{
  51. alignItems: 'center' , flexDirection: 'row' , marginVertical: 12 }} className="space-x-2 mx-[5%]">
  52. {carData.map((car, index) => (
  53. <ChooseCarForChargingRow key={`${car.name}+${index}`} image={car.image} onPress={()=> {
  54. setSelectedCar(car.id);
  55. console.log(car.id);
  56. }}
  57. isSelected={selectedCar === car.id}
  58. // imageUrl={image}
  59. VehicleName={car.name}
  60. isDefault={car.isDefault}
  61. />
  62. ))}
  63. </ScrollView>
  64. </View>
  65. )}
  66. </View>
  67. </View>
  68. </View>
  69. );
  70. };
  71. //reminder: scan qr code page, ic call should be false
  72. const ScanQrPage = () => {
  73. const { userID, setUserID } = useUserInfoStore();
  74. // State declarations
  75. const [permission, requestPermission] = useCameraPermissions();
  76. const [scanned, setScanned] = useState(false);
  77. const viewRef = useRef(null);
  78. const [scannedResult, setScannedResult] = useState('');
  79. const [selectedCar, setSelectedCar] = useState('');
  80. const now = new Date();
  81. const [loading, setLoading] = useState(true);
  82. const [loading2, setLoading2] = useState(false);
  83. const [loading3, setLoading3] = useState(false);
  84. const [carData, setCarData] = useState([]);
  85. // Effect for requesting camera permissions
  86. useEffect(() => {
  87. (async () => {
  88. const { status } = await requestPermission();
  89. if (status !== 'granted') {
  90. alert(
  91. '我們需要相機權限來掃描機器上的二維碼,以便識別並啟動充電機器。我們不會儲存或共享任何掃描到的資訊。 請前往設定開啟相機權限'
  92. );
  93. }
  94. })();
  95. }, []);
  96. // Effect for fetching user's cars
  97. useEffect(() => {
  98. const fetchAllCars = async () => {
  99. try {
  100. const response = await chargeStationService.getUserCars();
  101. if (response) {
  102. // console.log(response.data);
  103. const carTypes = response.data.map((item: any) => ({
  104. id: item.id,
  105. name: item.car_type.name,
  106. image: item.car_type.type_image_url
  107. }));
  108. console.log('carTypes', carTypes);
  109. // console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', carTypes);
  110. let updatedCarTypes = [...carTypes];
  111. for (let i = 0; i
  112. < carTypes.length; i++) { const car=updatedCarTypes[i]; const imageUrl=await
  113. chargeStationService.getProcessedImageUrl(car.image); updatedCarTypes[i]={ ...car, image: imageUrl }; }
  114. setCarData(updatedCarTypes); console.log('updatedCarTypes', updatedCarTypes); return true; } } catch (error) {
  115. console.log(error); } finally { setLoading(false); } }; fetchAllCars(); }, []); if (!permission) { return <View />;
  116. }
  117. if (!permission.granted) {
  118. return (
  119. <View className="flex-1 justify-center items-center">
  120. <Text style={{ textAlign: 'center' }}>
  121. 我們需要相機權限來掃描機器上的二維碼,以便識別並啟動充電機器。我們不會儲存或共享任何掃描到的資訊。
  122. 請前往設定開啟相機權限
  123. </Text>
  124. </View>
  125. );
  126. }
  127. // Function to handle barcode scanning
  128. const handleBarCodeScanned = async ({ bounds, data, type }: { bounds: any; data: any; type: any }) => {
  129. const { origin, size } = bounds;
  130. // Calculate the size of the square transparent area
  131. const transparentAreaSize = Math.min(screenWidth * 0.6, screenHeight * 0.3);
  132. const transparentAreaX = (screenWidth - transparentAreaSize) / 2;
  133. const transparentAreaY = (screenHeight - transparentAreaSize) / 2;
  134. // Check if the barcode is within the transparent area
  135. if (
  136. origin.x >= transparentAreaX &&
  137. origin.y >= transparentAreaY &&
  138. origin.x + size.width <= transparentAreaX + transparentAreaSize && origin.y + size.height <=transparentAreaY +
  139. transparentAreaSize ) { setScanned(true); setScannedResult(data); Vibration.vibrate(100); console.log(` type:
  140. ${type} data: ${data} typeofData ${typeof data}`); try { const response=await
  141. chargeStationService.getTodayReservation(); if (response) { const now=new Date(); const
  142. onlyThisConnector=response.filter( (reservation: any)=> reservation.connector.ConnectorID === data
  143. );
  144. onlyThisConnector.sort((a: any, b: any) => {
  145. const timeA = new Date(a.book_time).getTime();
  146. const timeB = new Date(b.book_time).getTime();
  147. return timeA - timeB;
  148. });
  149. let previousReservation = null;
  150. let upcomingReservation = null;
  151. for (let i = 0; i < onlyThisConnector.length; i++) { const reservationTime=new
  152. Date(onlyThisConnector[i].book_time).getTime(); if (reservationTime <=now.getTime()) {
  153. previousReservation=onlyThisConnector[i]; } else { upcomingReservation=onlyThisConnector[i]; break; } } const
  154. relevantReservations=[previousReservation || null, upcomingReservation || null]; console.log('Relevant
  155. reservations:', relevantReservations); const getNearestSlot=(date: Date)=> {
  156. const minutes = date.getMinutes();
  157. const nearestSlot = new Date(date);
  158. nearestSlot.setMinutes(minutes < 30 ? 0 : 30); nearestSlot.setSeconds(0); nearestSlot.setMilliseconds(0); return
  159. nearestSlot; }; const currentSlot=getNearestSlot(now); const nextSlot=new Date(currentSlot.getTime() + 30 *
  160. 60 * 1000); const AreOrdersAdjacentToNow=relevantReservations.map((reservation, index)=> {
  161. if (!reservation) return false;
  162. const reservationTime = new Date(reservation.book_time);
  163. return index === 0
  164. ? reservationTime.getTime() === currentSlot.getTime()
  165. : reservationTime.getTime() === nextSlot.getTime();
  166. });
  167. console.log('AreOrdersAdjacentToNow:', AreOrdersAdjacentToNow);
  168. if (!AreOrdersAdjacentToNow[0] && !AreOrdersAdjacentToNow[1]) {
  169. console.log('Charging machine is available. Starting charging process...');
  170. startCharging(data);
  171. } else if (AreOrdersAdjacentToNow[0]) {
  172. const previousReservation = relevantReservations[0];
  173. if (previousReservation && previousReservation.user.id === userID) {
  174. const reservationTime = new Date(previousReservation.book_time);
  175. const timeDifference = now.getTime() - reservationTime.getTime();
  176. const minutesDifference = timeDifference / (1000 * 60);
  177. if (minutesDifference <= 15) { console.log('User arrived within 15 minutes of their reservation.');
  178. startCharging(data); } else { console.log('User arrived more than 15 minutes late for their
  179. reservation.'); if (!AreOrdersAdjacentToNow[1]) { console.log('Next slot is available. Allowing charging
  180. despite late arrival.'); startCharging(data); } else { Alert.alert( '預約已過期'
  181. , '您的預約時間已經過期,且下一個時段已被預約。請重新預約。' ); console.log('Next slot is not available. Charging not allowed.'); }
  182. } } else { Alert.alert('無法使用', '此充電槍已經被預約,請選擇其他位置' ); } } else if (AreOrdersAdjacentToNow[1]) { const
  183. upcomingReservation=relevantReservations[1]; if (upcomingReservation &&
  184. upcomingReservation.user.id===userID) { const minutesUntilReservation=(new
  185. Date(upcomingReservation.book_time).getTime() - now.getTime()) / (1000 * 60); if
  186. (minutesUntilReservation <=5) { console.log('User arrived slightly early for their upcoming
  187. reservation.'); startCharging(data); } else { Alert.alert( '預約時間未到' , `您的預約時間還有
  188. ${Math.round(minutesUntilReservation)} 分鐘開始。請稍後再試。` ); } } else {
  189. Alert.alert('已被預約', '此充電槍已被其他用戶預約。請選擇其他位置。' ); } } else { Alert.alert('無法使用', '此充電槍目前無法使用。請選擇其他位置或稍後再試。'
  190. ); } } else { console.log('No response from getTodayReservation'); Alert.alert('系統錯誤', '無法獲取預約信息。請稍後再試。'
  191. ); } } catch (error) { console.error("Error fetching today's reservations:", error);
  192. Alert.alert('系統錯誤', '發生未知錯誤。請稍後再試。' ); } setTimeout(()=> {
  193. setScanned(false);
  194. }, 2000);
  195. }
  196. };
  197. //prepare data for submission
  198. const dataForSubmission = {
  199. stationID: '2405311022116801000',
  200. connector: scannedResult,
  201. user: userID,
  202. book_time: now,
  203. end_time: now,
  204. total_power: 0,
  205. total_fee: 0,
  206. promotion_code: '',
  207. car: selectedCar,
  208. type: 'walking',
  209. is_ic_call: false
  210. };
  211. const startCharging = async (scanResult: string) => {
  212. try {
  213. if (selectedCar === '') {
  214. Alert.alert('請選擇車輛');
  215. return;
  216. }
  217. const response = await walletService.submitPayment(
  218. dataForSubmission.stationID,
  219. scanResult,
  220. dataForSubmission.user,
  221. dataForSubmission.book_time,
  222. dataForSubmission.end_time,
  223. dataForSubmission.total_power,
  224. dataForSubmission.total_fee,
  225. dataForSubmission.promotion_code,
  226. dataForSubmission.car,
  227. dataForSubmission.type,
  228. dataForSubmission.is_ic_call
  229. );
  230. if (response) {
  231. console.log('Charging started from startCharging', response);
  232. router.push('(auth)/(tabs)/(charging)/chargingPage');
  233. } else {
  234. console.log('Failed to start chargi12312312ng:', response);
  235. Alert.alert('掃描失敗 請稍後再試。', response);
  236. }
  237. } catch (error) {
  238. console.log('Failed to start charging:', error);
  239. }
  240. };
  241. return (
  242. <View style={styles.container} ref={viewRef}>
  243. {loading ? (
  244. <View className="flex-1 items-center justify-center">
  245. <ActivityIndicator />
  246. </View>
  247. ) : (
  248. <CameraView style={styles.camera} facing="back" barcodeScannerSettings={{ barcodeTypes: ['qr'] }}
  249. onBarcodeScanned={scanned ? undefined : handleBarCodeScanned}
  250. responsiveOrientationWhenOrientationLocked={true}>
  251. <View style={styles.overlay}>
  252. <View style={styles.topOverlay}>
  253. <ChooseCar carData={carData} loading={loading} selectedCar={selectedCar}
  254. setSelectedCar={setSelectedCar} />
  255. </View>
  256. <View style={styles.centerRow}>
  257. <View style={styles.leftOverlay}></View>
  258. <View style={styles.transparentArea}></View>
  259. <View style={styles.rightOverlay} />
  260. </View>
  261. <View className="items-center justify-between" style={styles.bottomOverlay}>
  262. <View>
  263. <Text className="text-white text-lg font-bold mt-2 text-center">
  264. 請選擇充電車輛{'\n'}及掃瞄充電座上的二維碼
  265. </Text>
  266. </View>
  267. <View className="flex-row space-x-2 items-center ">
  268. <QuestionSvg />
  269. <Pressable onPress={()=> router.push('assistancePage')}>
  270. <Text className="text-white text-base">需要協助?</Text>
  271. </Pressable>
  272. </View>
  273. <View />
  274. </View>
  275. </View>
  276. </CameraView>
  277. )}
  278. </View>
  279. );
  280. };
  281. const styles = StyleSheet.create({
  282. container: {
  283. flex: 1
  284. },
  285. camera: {
  286. flex: 1
  287. },
  288. overlay: {
  289. flex: 1
  290. },
  291. topOverlay: {
  292. flex: 35,
  293. alignItems: 'center',
  294. backgroundColor: 'rgba(0,0,0,0.5)'
  295. },
  296. centerRow: {
  297. flex: 30,
  298. flexDirection: 'row'
  299. },
  300. leftOverlay: {
  301. flex: 20,
  302. backgroundColor: 'rgba(0,0,0,0.5)'
  303. },
  304. transparentArea: {
  305. flex: 60,
  306. aspectRatio: 1,
  307. position: 'relative'
  308. },
  309. rightOverlay: {
  310. flex: 20,
  311. backgroundColor: 'rgba(0,0,0,0.5)'
  312. },
  313. bottomOverlay: {
  314. flex: 35,
  315. backgroundColor: 'rgba(0,0,0,0.5)'
  316. }
  317. });
  318. export default ScanQrPage;