367 lines
10 KiB
TypeScript
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 = 5000; // 5초마다 위치 업데이트
|
|
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;
|
|
|