|
|
@@ -1,345 +0,0 @@
|
|
|
-import { CameraView, useCameraPermissions } from 'expo-camera';
|
|
|
-import { useEffect, useRef, useState } from 'react';
|
|
|
-import {
|
|
|
-ActivityIndicator,
|
|
|
-Alert,
|
|
|
-Dimensions,
|
|
|
-Pressable,
|
|
|
-ScrollView,
|
|
|
-StyleSheet,
|
|
|
-Text,
|
|
|
-Vibration,
|
|
|
-View
|
|
|
-} from 'react-native';
|
|
|
-import ChooseCarForChargingRow from '../../../../component/global/chooseCarForChargingRow';
|
|
|
-import { CrossLogoWhiteSvg, QuestionSvg } from '../../../../component/global/SVG';
|
|
|
-import { router } from 'expo-router';
|
|
|
-import { chargeStationService } from '../../../../service/chargeStationService';
|
|
|
-import { authenticationService } from '../../../../service/authService';
|
|
|
-import { walletService } from '../../../../service/walletService';
|
|
|
-import useUserInfoStore from '../../../../providers/userinfo_store';
|
|
|
-const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
|
|
|
-
|
|
|
-const ChooseCar = ({ carData, loading, selectedCar, setSelectedCar }) => {
|
|
|
-const isLargeScreen = screenHeight >= 800;
|
|
|
-const defaultImageUrl = require('../../../../assets/car1.png');
|
|
|
-
|
|
|
-return (
|
|
|
-<View style={{ ...(isLargeScreen ? { marginTop: '10%' , marginBottom: '12%' , paddingBottom: 12 } : { flex: 1,
|
|
|
- alignItems: 'center' , justifyContent: 'center' }) }}>
|
|
|
- <View className="justify-center items-center flex-1 ">
|
|
|
- <View style={{ ...(isLargeScreen ? {} : { backgroundColor: 'rgba(0,0,0,0.7)' }) }}>
|
|
|
- {loading ? (
|
|
|
- <View className="w-full">
|
|
|
- <ActivityIndicator color="#34657b" />
|
|
|
- </View>
|
|
|
- ) : (
|
|
|
- <View className="w-screen bg-[#000000B3]">
|
|
|
- <View className="flex-row items-center justify-between mx-[5%] ">
|
|
|
- <Pressable className="pt-4 " onPress={()=> {
|
|
|
- if (router.canGoBack()) {
|
|
|
- router.back();
|
|
|
- } else {
|
|
|
- router.replace('mainPage');
|
|
|
- }
|
|
|
- }}
|
|
|
- >
|
|
|
- <CrossLogoWhiteSvg />
|
|
|
- </Pressable>
|
|
|
-
|
|
|
- <Text className="text-base text-white pt-2">選擇充電車輛</Text>
|
|
|
- <Text className="text-xl text-white pt-2"></Text>
|
|
|
- </View>
|
|
|
-
|
|
|
- <ScrollView horizontal={true} showsHorizontalScrollIndicator={false} contentContainerStyle={{
|
|
|
- alignItems: 'center' , flexDirection: 'row' , marginVertical: 12 }} className="space-x-2 mx-[5%]">
|
|
|
- {carData.map((car, index) => (
|
|
|
- <ChooseCarForChargingRow key={`${car.name}+${index}`} image={car.image} onPress={()=> {
|
|
|
- setSelectedCar(car.id);
|
|
|
- console.log(car.id);
|
|
|
- }}
|
|
|
- isSelected={selectedCar === car.id}
|
|
|
- // imageUrl={image}
|
|
|
- VehicleName={car.name}
|
|
|
- isDefault={car.isDefault}
|
|
|
- />
|
|
|
- ))}
|
|
|
- </ScrollView>
|
|
|
- </View>
|
|
|
- )}
|
|
|
- </View>
|
|
|
- </View>
|
|
|
-</View>
|
|
|
-);
|
|
|
-};
|
|
|
-
|
|
|
-//reminder: scan qr code page, ic call should be false
|
|
|
-const ScanQrPage = () => {
|
|
|
-const { userID, setUserID } = useUserInfoStore();
|
|
|
-// State declarations
|
|
|
-const [permission, requestPermission] = useCameraPermissions();
|
|
|
-const [scanned, setScanned] = useState(false);
|
|
|
-const viewRef = useRef(null);
|
|
|
-const [scannedResult, setScannedResult] = useState('');
|
|
|
-const [selectedCar, setSelectedCar] = useState('');
|
|
|
-
|
|
|
-const now = new Date();
|
|
|
-const [loading, setLoading] = useState(true);
|
|
|
-const [loading2, setLoading2] = useState(false);
|
|
|
-const [loading3, setLoading3] = useState(false);
|
|
|
-const [carData, setCarData] = useState([]);
|
|
|
-
|
|
|
-// Effect for requesting camera permissions
|
|
|
-useEffect(() => {
|
|
|
-(async () => {
|
|
|
-const { status } = await requestPermission();
|
|
|
-if (status !== 'granted') {
|
|
|
-alert(
|
|
|
-'我們需要相機權限來掃描機器上的二維碼,以便識別並啟動充電機器。我們不會儲存或共享任何掃描到的資訊。 請前往設定開啟相機權限'
|
|
|
-);
|
|
|
-}
|
|
|
-})();
|
|
|
-}, []);
|
|
|
-
|
|
|
-// Effect for fetching user's cars
|
|
|
-useEffect(() => {
|
|
|
-const fetchAllCars = async () => {
|
|
|
-try {
|
|
|
-const response = await chargeStationService.getUserCars();
|
|
|
-if (response) {
|
|
|
-// console.log(response.data);
|
|
|
-const carTypes = response.data.map((item: any) => ({
|
|
|
-id: item.id,
|
|
|
-name: item.car_type.name,
|
|
|
-image: item.car_type.type_image_url
|
|
|
-}));
|
|
|
-console.log('carTypes', carTypes);
|
|
|
-// console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', carTypes);
|
|
|
-let updatedCarTypes = [...carTypes];
|
|
|
-for (let i = 0; i
|
|
|
-< carTypes.length; i++) { const car=updatedCarTypes[i]; const imageUrl=await
|
|
|
- chargeStationService.getProcessedImageUrl(car.image); updatedCarTypes[i]={ ...car, image: imageUrl }; }
|
|
|
- setCarData(updatedCarTypes); console.log('updatedCarTypes', updatedCarTypes); return true; } } catch (error) {
|
|
|
- console.log(error); } finally { setLoading(false); } }; fetchAllCars(); }, []); if (!permission) { return <View />;
|
|
|
-}
|
|
|
-
|
|
|
-if (!permission.granted) {
|
|
|
-return (
|
|
|
-<View className="flex-1 justify-center items-center">
|
|
|
- <Text style={{ textAlign: 'center' }}>
|
|
|
- 我們需要相機權限來掃描機器上的二維碼,以便識別並啟動充電機器。我們不會儲存或共享任何掃描到的資訊。
|
|
|
- 請前往設定開啟相機權限
|
|
|
- </Text>
|
|
|
-</View>
|
|
|
-);
|
|
|
-}
|
|
|
-
|
|
|
-// Function to handle barcode scanning
|
|
|
-const handleBarCodeScanned = async ({ bounds, data, type }: { bounds: any; data: any; type: any }) => {
|
|
|
-const { origin, size } = bounds;
|
|
|
-// Calculate the size of the square transparent area
|
|
|
-const transparentAreaSize = Math.min(screenWidth * 0.6, screenHeight * 0.3);
|
|
|
-const transparentAreaX = (screenWidth - transparentAreaSize) / 2;
|
|
|
-const transparentAreaY = (screenHeight - transparentAreaSize) / 2;
|
|
|
-
|
|
|
-// Check if the barcode is within the transparent area
|
|
|
-if (
|
|
|
-origin.x >= transparentAreaX &&
|
|
|
-origin.y >= transparentAreaY &&
|
|
|
-origin.x + size.width <= transparentAreaX + transparentAreaSize && origin.y + size.height <=transparentAreaY +
|
|
|
- transparentAreaSize ) { setScanned(true); setScannedResult(data); Vibration.vibrate(100); console.log(` type:
|
|
|
- ${type} data: ${data} typeofData ${typeof data}`); try { const response=await
|
|
|
- chargeStationService.getTodayReservation(); if (response) { const now=new Date(); const
|
|
|
- onlyThisConnector=response.filter( (reservation: any)=> reservation.connector.ConnectorID === data
|
|
|
- );
|
|
|
-
|
|
|
- onlyThisConnector.sort((a: any, b: any) => {
|
|
|
- const timeA = new Date(a.book_time).getTime();
|
|
|
- const timeB = new Date(b.book_time).getTime();
|
|
|
- return timeA - timeB;
|
|
|
- });
|
|
|
-
|
|
|
- let previousReservation = null;
|
|
|
- let upcomingReservation = null;
|
|
|
-
|
|
|
- for (let i = 0; i < onlyThisConnector.length; i++) { const reservationTime=new
|
|
|
- Date(onlyThisConnector[i].book_time).getTime(); if (reservationTime <=now.getTime()) {
|
|
|
- previousReservation=onlyThisConnector[i]; } else { upcomingReservation=onlyThisConnector[i]; break; } } const
|
|
|
- relevantReservations=[previousReservation || null, upcomingReservation || null]; console.log('Relevant
|
|
|
- reservations:', relevantReservations); const getNearestSlot=(date: Date)=> {
|
|
|
- const minutes = date.getMinutes();
|
|
|
- const nearestSlot = new Date(date);
|
|
|
- nearestSlot.setMinutes(minutes < 30 ? 0 : 30); nearestSlot.setSeconds(0); nearestSlot.setMilliseconds(0); return
|
|
|
- nearestSlot; }; const currentSlot=getNearestSlot(now); const nextSlot=new Date(currentSlot.getTime() + 30 *
|
|
|
- 60 * 1000); const AreOrdersAdjacentToNow=relevantReservations.map((reservation, index)=> {
|
|
|
- if (!reservation) return false;
|
|
|
-
|
|
|
- const reservationTime = new Date(reservation.book_time);
|
|
|
- return index === 0
|
|
|
- ? reservationTime.getTime() === currentSlot.getTime()
|
|
|
- : reservationTime.getTime() === nextSlot.getTime();
|
|
|
- });
|
|
|
-
|
|
|
- console.log('AreOrdersAdjacentToNow:', AreOrdersAdjacentToNow);
|
|
|
-
|
|
|
- if (!AreOrdersAdjacentToNow[0] && !AreOrdersAdjacentToNow[1]) {
|
|
|
- console.log('Charging machine is available. Starting charging process...');
|
|
|
- startCharging(data);
|
|
|
- } else if (AreOrdersAdjacentToNow[0]) {
|
|
|
- const previousReservation = relevantReservations[0];
|
|
|
- if (previousReservation && previousReservation.user.id === userID) {
|
|
|
- const reservationTime = new Date(previousReservation.book_time);
|
|
|
- const timeDifference = now.getTime() - reservationTime.getTime();
|
|
|
- const minutesDifference = timeDifference / (1000 * 60);
|
|
|
-
|
|
|
- if (minutesDifference <= 15) { console.log('User arrived within 15 minutes of their reservation.');
|
|
|
- startCharging(data); } else { console.log('User arrived more than 15 minutes late for their
|
|
|
- reservation.'); if (!AreOrdersAdjacentToNow[1]) { console.log('Next slot is available. Allowing charging
|
|
|
- despite late arrival.'); startCharging(data); } else { Alert.alert( '預約已過期'
|
|
|
- , '您的預約時間已經過期,且下一個時段已被預約。請重新預約。' ); console.log('Next slot is not available. Charging not allowed.'); }
|
|
|
- } } else { Alert.alert('無法使用', '此充電槍已經被預約,請選擇其他位置' ); } } else if (AreOrdersAdjacentToNow[1]) { const
|
|
|
- upcomingReservation=relevantReservations[1]; if (upcomingReservation &&
|
|
|
- upcomingReservation.user.id===userID) { const minutesUntilReservation=(new
|
|
|
- Date(upcomingReservation.book_time).getTime() - now.getTime()) / (1000 * 60); if
|
|
|
- (minutesUntilReservation <=5) { console.log('User arrived slightly early for their upcoming
|
|
|
- reservation.'); startCharging(data); } else { Alert.alert( '預約時間未到' , `您的預約時間還有
|
|
|
- ${Math.round(minutesUntilReservation)} 分鐘開始。請稍後再試。` ); } } else {
|
|
|
- Alert.alert('已被預約', '此充電槍已被其他用戶預約。請選擇其他位置。' ); } } else { Alert.alert('無法使用', '此充電槍目前無法使用。請選擇其他位置或稍後再試。'
|
|
|
- ); } } else { console.log('No response from getTodayReservation'); Alert.alert('系統錯誤', '無法獲取預約信息。請稍後再試。'
|
|
|
- ); } } catch (error) { console.error("Error fetching today's reservations:", error);
|
|
|
- Alert.alert('系統錯誤', '發生未知錯誤。請稍後再試。' ); } setTimeout(()=> {
|
|
|
- setScanned(false);
|
|
|
- }, 2000);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- //prepare data for submission
|
|
|
- const dataForSubmission = {
|
|
|
- stationID: '2405311022116801000',
|
|
|
- connector: scannedResult,
|
|
|
- user: userID,
|
|
|
- book_time: now,
|
|
|
- end_time: now,
|
|
|
- total_power: 0,
|
|
|
- total_fee: 0,
|
|
|
- promotion_code: '',
|
|
|
- car: selectedCar,
|
|
|
- type: 'walking',
|
|
|
- is_ic_call: false
|
|
|
- };
|
|
|
-
|
|
|
- const startCharging = async (scanResult: string) => {
|
|
|
- try {
|
|
|
- if (selectedCar === '') {
|
|
|
- Alert.alert('請選擇車輛');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const response = await walletService.submitPayment(
|
|
|
- dataForSubmission.stationID,
|
|
|
- scanResult,
|
|
|
- dataForSubmission.user,
|
|
|
- dataForSubmission.book_time,
|
|
|
- dataForSubmission.end_time,
|
|
|
- dataForSubmission.total_power,
|
|
|
- dataForSubmission.total_fee,
|
|
|
- dataForSubmission.promotion_code,
|
|
|
- dataForSubmission.car,
|
|
|
- dataForSubmission.type,
|
|
|
- dataForSubmission.is_ic_call
|
|
|
- );
|
|
|
-
|
|
|
- if (response) {
|
|
|
- console.log('Charging started from startCharging', response);
|
|
|
- router.push('(auth)/(tabs)/(charging)/chargingPage');
|
|
|
- } else {
|
|
|
- console.log('Failed to start chargi12312312ng:', response);
|
|
|
- Alert.alert('掃描失敗 請稍後再試。', response);
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.log('Failed to start charging:', error);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- return (
|
|
|
- <View style={styles.container} ref={viewRef}>
|
|
|
- {loading ? (
|
|
|
- <View className="flex-1 items-center justify-center">
|
|
|
- <ActivityIndicator />
|
|
|
- </View>
|
|
|
- ) : (
|
|
|
- <CameraView style={styles.camera} facing="back" barcodeScannerSettings={{ barcodeTypes: ['qr'] }}
|
|
|
- onBarcodeScanned={scanned ? undefined : handleBarCodeScanned}
|
|
|
- responsiveOrientationWhenOrientationLocked={true}>
|
|
|
- <View style={styles.overlay}>
|
|
|
- <View style={styles.topOverlay}>
|
|
|
- <ChooseCar carData={carData} loading={loading} selectedCar={selectedCar}
|
|
|
- setSelectedCar={setSelectedCar} />
|
|
|
- </View>
|
|
|
- <View style={styles.centerRow}>
|
|
|
- <View style={styles.leftOverlay}></View>
|
|
|
- <View style={styles.transparentArea}></View>
|
|
|
- <View style={styles.rightOverlay} />
|
|
|
- </View>
|
|
|
- <View className="items-center justify-between" style={styles.bottomOverlay}>
|
|
|
- <View>
|
|
|
- <Text className="text-white text-lg font-bold mt-2 text-center">
|
|
|
- 請選擇充電車輛{'\n'}及掃瞄充電座上的二維碼
|
|
|
- </Text>
|
|
|
- </View>
|
|
|
- <View className="flex-row space-x-2 items-center ">
|
|
|
- <QuestionSvg />
|
|
|
-
|
|
|
- <Pressable onPress={()=> router.push('assistancePage')}>
|
|
|
- <Text className="text-white text-base">需要協助?</Text>
|
|
|
- </Pressable>
|
|
|
- </View>
|
|
|
- <View />
|
|
|
- </View>
|
|
|
- </View>
|
|
|
- </CameraView>
|
|
|
- )}
|
|
|
- </View>
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- const styles = StyleSheet.create({
|
|
|
- container: {
|
|
|
- flex: 1
|
|
|
- },
|
|
|
- camera: {
|
|
|
- flex: 1
|
|
|
- },
|
|
|
- overlay: {
|
|
|
- flex: 1
|
|
|
- },
|
|
|
- topOverlay: {
|
|
|
- flex: 35,
|
|
|
- alignItems: 'center',
|
|
|
- backgroundColor: 'rgba(0,0,0,0.5)'
|
|
|
- },
|
|
|
- centerRow: {
|
|
|
- flex: 30,
|
|
|
- flexDirection: 'row'
|
|
|
- },
|
|
|
- leftOverlay: {
|
|
|
- flex: 20,
|
|
|
- backgroundColor: 'rgba(0,0,0,0.5)'
|
|
|
- },
|
|
|
- transparentArea: {
|
|
|
- flex: 60,
|
|
|
- aspectRatio: 1,
|
|
|
- position: 'relative'
|
|
|
- },
|
|
|
-
|
|
|
- rightOverlay: {
|
|
|
- flex: 20,
|
|
|
- backgroundColor: 'rgba(0,0,0,0.5)'
|
|
|
- },
|
|
|
- bottomOverlay: {
|
|
|
- flex: 35,
|
|
|
- backgroundColor: 'rgba(0,0,0,0.5)'
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- export default ScanQrPage;
|