Files
DTGAPK/App.tsx

367 lines
10 KiB
TypeScript

import React, { useEffect, useRef, useState } from 'react';
import {
SafeAreaView,
StatusBar,
StyleSheet,
PermissionsAndroid,
Platform,
Alert,
AppState,
} from 'react-native';
import { WebView } from 'react-native-webview';
import Geolocation from 'react-native-geolocation-service';
import AsyncStorage from '@react-native-async-storage/async-storage';
import BackgroundTimer from 'react-native-background-timer';
import axios from 'axios';
const WEBSITE_URL = 'https://logistream.kpslp.kr';
const LOCATION_UPDATE_INTERVAL = 10000; // 10초마다 위치 업데이트
const API_ENDPOINT = 'https://logistream.kpslp.kr/api/location'; // 서버 API 엔드포인트
interface LocationData {
latitude: number;
longitude: number;
accuracy: number;
altitude: number | null;
heading: number | null;
speed: number | null;
timestamp: number;
}
function App(): React.JSX.Element {
const webViewRef = useRef<WebView>(null);
const [hasLocationPermission, setHasLocationPermission] = useState(false);
const [userInfo, setUserInfo] = useState<any>(null);
const locationIntervalRef = useRef<number | null>(null);
// 위치 권한 요청 (Android)
const requestLocationPermissionAndroid = async () => {
try {
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION,
]);
if (
granted['android.permission.ACCESS_FINE_LOCATION'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
setHasLocationPermission(true);
return true;
} else {
Alert.alert('권한 필요', '위치 권한이 필요합니다.');
return false;
}
} catch (err) {
console.warn(err);
return false;
}
};
// 위치 권한 요청 (iOS)
const requestLocationPermissionIOS = async () => {
try {
const result = await Geolocation.requestAuthorization('always');
if (result === 'granted' || result === 'whenInUse') {
setHasLocationPermission(true);
return true;
} else {
Alert.alert('권한 필요', '위치 권한이 필요합니다.');
return false;
}
} catch (error) {
console.error('iOS 위치 권한 오류:', error);
return false;
}
};
// 위치 정보 가져오기
const getCurrentLocation = (): Promise<LocationData> => {
return new Promise((resolve, reject) => {
Geolocation.getCurrentPosition(
(position) => {
const locationData: LocationData = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude,
heading: position.coords.heading,
speed: position.coords.speed,
timestamp: position.timestamp,
};
resolve(locationData);
},
(error) => {
console.error('위치 정보 오류:', error);
reject(error);
},
{
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 10000,
showLocationDialog: true,
}
);
});
};
// 서버로 위치 정보 전송
const sendLocationToServer = async (locationData: LocationData) => {
try {
const userData = await AsyncStorage.getItem('userInfo');
const parsedUserData = userData ? JSON.parse(userData) : null;
if (!parsedUserData) {
console.log('사용자 정보가 없습니다. 로그인이 필요합니다.');
return;
}
const payload = {
userId: parsedUserData.userId,
userName: parsedUserData.userName,
userEmail: parsedUserData.userEmail,
location: locationData,
deviceInfo: {
platform: Platform.OS,
version: Platform.Version,
},
};
console.log('서버로 위치 전송:', payload);
const response = await axios.post(API_ENDPOINT, payload, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${parsedUserData.token || ''}`,
},
timeout: 10000,
});
console.log('서버 응답:', response.data);
} catch (error) {
console.error('위치 전송 오류:', error);
}
};
// 위치 추적 시작
const startLocationTracking = () => {
if (locationIntervalRef.current) {
return; // 이미 실행 중
}
console.log('위치 추적 시작');
// 즉시 첫 번째 위치 전송
getCurrentLocation()
.then(sendLocationToServer)
.catch(console.error);
// 백그라운드에서도 작동하는 타이머
locationIntervalRef.current = BackgroundTimer.setInterval(() => {
getCurrentLocation()
.then(sendLocationToServer)
.catch(console.error);
}, LOCATION_UPDATE_INTERVAL);
};
// 위치 추적 중지
const stopLocationTracking = () => {
if (locationIntervalRef.current) {
BackgroundTimer.clearInterval(locationIntervalRef.current);
locationIntervalRef.current = null;
console.log('위치 추적 중지');
}
};
// 웹뷰에서 메시지 수신
const handleWebViewMessage = async (event: any) => {
try {
const data = JSON.parse(event.nativeEvent.data);
console.log('웹뷰로부터 메시지 수신:', data);
switch (data.type) {
case 'LOGIN_SUCCESS':
// 로그인 성공 시 사용자 정보 저장 및 위치 추적 시작
await AsyncStorage.setItem('userInfo', JSON.stringify(data.userInfo));
setUserInfo(data.userInfo);
startLocationTracking();
break;
case 'LOGOUT':
// 로그아웃 시 위치 추적 중지 및 사용자 정보 삭제
stopLocationTracking();
await AsyncStorage.removeItem('userInfo');
setUserInfo(null);
break;
case 'REQUEST_LOCATION':
// 웹에서 현재 위치 요청
const location = await getCurrentLocation();
webViewRef.current?.postMessage(
JSON.stringify({
type: 'LOCATION_UPDATE',
location,
})
);
break;
case 'START_TRACKING':
// 위치 추적 시작 요청
startLocationTracking();
break;
case 'STOP_TRACKING':
// 위치 추적 중지 요청
stopLocationTracking();
break;
default:
console.log('알 수 없는 메시지 타입:', data.type);
}
} catch (error) {
console.error('메시지 처리 오류:', error);
}
};
// 웹뷰에 네이티브 정보 전송
const sendNativeInfoToWeb = () => {
const nativeInfo = {
type: 'NATIVE_INFO',
platform: Platform.OS,
version: Platform.Version,
hasLocationPermission,
};
webViewRef.current?.postMessage(JSON.stringify(nativeInfo));
};
// 앱 초기화
useEffect(() => {
const initApp = async () => {
// 위치 권한 요청
if (Platform.OS === 'android') {
await requestLocationPermissionAndroid();
} else {
await requestLocationPermissionIOS();
}
// 저장된 사용자 정보 확인
const savedUserInfo = await AsyncStorage.getItem('userInfo');
if (savedUserInfo) {
const parsedUserInfo = JSON.parse(savedUserInfo);
setUserInfo(parsedUserInfo);
// 자동으로 위치 추적 시작
startLocationTracking();
}
};
initApp();
// 앱 상태 변경 감지
const subscription = AppState.addEventListener('change', (nextAppState) => {
console.log('앱 상태 변경:', nextAppState);
// 앱이 포그라운드로 돌아올 때 웹뷰에 정보 전송
if (nextAppState === 'active') {
sendNativeInfoToWeb();
}
});
// 클린업
return () => {
stopLocationTracking();
subscription.remove();
};
}, []);
// 웹뷰 JavaScript 주입 코드
const injectedJavaScript = `
(function() {
// 네이티브로 메시지 전송 함수
window.sendToNative = function(type, data) {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: type,
...data
}));
};
// 네이티브로부터 메시지 수신
window.addEventListener('message', function(event) {
try {
const data = JSON.parse(event.data);
console.log('네이티브로부터 메시지:', data);
// 커스텀 이벤트 발생
const customEvent = new CustomEvent('nativeMessage', { detail: data });
window.dispatchEvent(customEvent);
} catch (error) {
console.error('메시지 파싱 오류:', error);
}
});
// 로그인 성공 시 호출할 함수 (웹 페이지에서 사용)
window.notifyLoginSuccess = function(userInfo) {
window.sendToNative('LOGIN_SUCCESS', { userInfo: userInfo });
};
// 로그아웃 시 호출할 함수
window.notifyLogout = function() {
window.sendToNative('LOGOUT', {});
};
// 현재 위치 요청 함수
window.requestCurrentLocation = function() {
window.sendToNative('REQUEST_LOCATION', {});
};
// 위치 추적 시작/중지
window.startLocationTracking = function() {
window.sendToNative('START_TRACKING', {});
};
window.stopLocationTracking = function() {
window.sendToNative('STOP_TRACKING', {});
};
console.log('네이티브 브릿지 초기화 완료');
})();
true;
`;
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<WebView
ref={webViewRef}
source={{ uri: WEBSITE_URL }}
style={styles.webview}
onMessage={handleWebViewMessage}
injectedJavaScript={injectedJavaScript}
onLoadEnd={sendNativeInfoToWeb}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
scalesPageToFit={true}
mixedContentMode="always"
allowsBackForwardNavigationGestures={true}
onError={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
console.error('웹뷰 오류:', nativeEvent);
}}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ffffff',
},
webview: {
flex: 1,
},
});
export default App;