From 88dcf853e210936a8c16c229ab028b3d0d8a5be4 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 24 Jan 2025 13:30:52 +0900 Subject: [PATCH 01/45] chore: install geolocation, permissions --- package-lock.json | 31 +++++++++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 33 insertions(+) diff --git a/package-lock.json b/package-lock.json index b1cc1c7..c3d4f86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@emotion/native": "^11.11.0", "@emotion/react": "^11.14.0", "@mj-studio/react-native-naver-map": "^2.2.0", + "@react-native-community/geolocation": "^3.4.0", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "@react-navigation/native-stack": "^7.2.0", @@ -22,6 +23,7 @@ "react-native-dropdown-picker": "^5.4.6", "react-native-error-boundary": "^1.2.7", "react-native-linear-gradient": "^2.8.3", + "react-native-permissions": "^5.2.1", "react-native-safe-area-context": "^5.0.0", "react-native-screens": "^4.4.0", "react-native-toast-message": "^2.2.1", @@ -4055,6 +4057,19 @@ } } }, + "node_modules/@react-native-community/geolocation": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@react-native-community/geolocation/-/geolocation-3.4.0.tgz", + "integrity": "sha512-bzZH89/cwmpkPMKKveoC72C4JH0yF4St5Ceg/ZM9pA1SqX9MlRIrIrrOGZ/+yi++xAvFDiYfihtn9TvXWU9/rA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@react-native-community/slider": { "version": "4.5.5", "resolved": "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.5.5.tgz", @@ -14578,6 +14593,22 @@ "prop-types": "^15.5.10" } }, + "node_modules/react-native-permissions": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-5.2.4.tgz", + "integrity": "sha512-WmFY3mDBwj0eO93ziEi8r+6uuZo6XrcEIdQ66AfzC8CeNXXclDwypTnewBj51K0mn4QVRJxM6PSgIlj0ii5qig==", + "license": "MIT", + "peerDependencies": { + "react": ">=18.1.0", + "react-native": ">=0.70.0", + "react-native-windows": ">=0.70.0" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } + }, "node_modules/react-native-reanimated": { "version": "3.16.7", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.7.tgz", diff --git a/package.json b/package.json index bbe8f0c..b7e80fd 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "@emotion/native": "^11.11.0", "@emotion/react": "^11.14.0", "@mj-studio/react-native-naver-map": "^2.2.0", + "@react-native-community/geolocation": "^3.4.0", + "react-native-permissions": "^5.2.1", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "@react-navigation/native-stack": "^7.2.0", From 13e443774baaa9580278c0198b401b71a089ed69 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 24 Jan 2025 13:31:05 +0900 Subject: [PATCH 02/45] =?UTF-8?q?chore:=20permission=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ios/Podfile b/ios/Podfile index e3ba7ca..cc7def8 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -5,9 +5,17 @@ require Pod::Executable.execute_command('node', ['-p', {paths: [process.argv[1]]}, )', __dir__]).strip +require_relative '../node_modules/react-native-permissions/scripts/setup' + platform :ios, min_ios_version_supported prepare_react_native_project! +setup_permissions([ + 'LocationAccuracy', + 'LocationAlways', + 'LocationWhenInUse', +]) + linkage = ENV['USE_FRAMEWORKS'] if linkage != nil Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green From 9dca2e8a4d4a09e81c6dcba748e75a24478690b8 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 24 Jan 2025 13:32:00 +0900 Subject: [PATCH 03/45] =?UTF-8?q?feat:=20=EC=9D=BC=EC=A0=95=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EB=A7=88=EB=8B=A4=20=ED=98=84=EC=9E=AC=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20=EB=B0=8F=20?= =?UTF-8?q?=EC=82=B0=EC=B1=85=20=EC=A4=91=20=EB=AA=A8=EB=93=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=95=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 197 +++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 4 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 4ede62a..1935fb1 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -1,13 +1,56 @@ -import { NaverMapView } from '@mj-studio/react-native-naver-map'; -import { useEffect, useState } from 'react'; +import { + Camera, + NaverMapMarkerOverlay, + NaverMapView, + NaverMapViewRef, + NaverMapCircleOverlay, +} from '@mj-studio/react-native-naver-map'; +import { useEffect, useRef, useState } from 'react'; +import { Platform } from 'react-native'; +import Geolocation from '@react-native-community/geolocation'; +import { request, PERMISSIONS, requestLocationAccuracy, requestMultiple } from 'react-native-permissions'; import { formatDuration, formatDistance } from '~screens/Home/WalkScreen'; import * as S from './styles'; +const WALKING_INTERVAL = 5000; +const NORMAL_INTERVAL = 10000; +const MIN_ACCURACY = 30; + const MapView = () => { + const mapRef = useRef(null); const [isWalking, setIsWalking] = useState(false); const [walkTime, setWalkTime] = useState(0); const [distance, _setDistance] = useState(0); + const [camera, _setCamera] = useState({ + latitude: 37.50497126, + longitude: 127.04905021, + zoom: 18, + }); + + const [_previousLocation, setPreviousLocation] = useState<{ + latitude: number; + longitude: number; + } | null>(null); + + const [currentLocation, setCurrentLocation] = useState<{ + latitude: number; + longitude: number; + }>({ + latitude: 37.50497126, + longitude: 127.04905021, + }); + + const [locationMarkers, setLocationMarkers] = useState< + { + latitude: number; + longitude: number; + index: number; + }[] + >([]); + + const [lastUpdateTime, setLastUpdateTime] = useState(Date.now()); + useEffect(() => { let interval: NodeJS.Timeout; if (isWalking) { @@ -18,6 +61,130 @@ const MapView = () => { return () => clearInterval(interval); }, [isWalking]); + const filterPosition = (position: { coords: { accuracy: number; latitude: number; longitude: number } }): boolean => { + return position.coords.accuracy <= MIN_ACCURACY; + }; + + useEffect(() => { + console.log('위치 추적 시작:', { isWalking }); + + const requestLocationPermission = async () => { + if (Platform.OS === 'ios') { + const status = await request(PERMISSIONS.IOS.LOCATION_ALWAYS); + console.log('iOS 위치 권한 상태:', status); + if (status === 'granted') { + try { + await requestLocationAccuracy({ purposeKey: 'common-purpose' }); + console.log('iOS 위치 정확도 설정 성공'); + } catch (e) { + console.error('iOS 위치 정확도 요청 실패:', e); + } + } + } else { + try { + const result = await requestMultiple([ + PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, + PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION, + ]); + console.log('Android 위치 권한 상태:', result); + } catch (e) { + console.error('Android 위치 권한 요청 실패:', e); + } + } + }; + + requestLocationPermission(); + + const watchId = Geolocation.watchPosition( + position => { + const currentTime = Date.now(); + const timeSinceLastUpdate = currentTime - lastUpdateTime; + + // 마지막 업데이트로부터 5초가 지나지 않았다면 무시 + if (timeSinceLastUpdate < WALKING_INTERVAL) { + console.log('업데이트 무시: 시간 간격이 충분하지 않음', { + timeSinceLastUpdate, + requiredInterval: WALKING_INTERVAL, + }); + return; + } + + console.log('위치 업데이트 받음:', { + accuracy: position.coords.accuracy, + latitude: position.coords.latitude, + longitude: position.coords.longitude, + timestamp: new Date(position.timestamp).toLocaleString(), + timeSinceLastUpdate, + isWalking, + }); + + if (!filterPosition(position)) { + console.log('위치 정확도가 낮음:', { + accuracy: position.coords.accuracy, + required: MIN_ACCURACY, + }); + return; + } + + const { latitude, longitude } = position.coords; + const newPosition = { latitude, longitude }; + + if (isWalking) { + setLocationMarkers(prev => [ + ...prev, + { + latitude, + longitude, + index: prev.length + 1, + }, + ]); + console.log('새 위치 마커 추가됨:', locationMarkers.length + 1); + + mapRef.current?.animateCameraTo({ + latitude, + longitude, + zoom: 18, + duration: 500, + easing: 'Fly', + }); + } + + setPreviousLocation(currentLocation); + setCurrentLocation(newPosition); + setLastUpdateTime(currentTime); + }, + error => { + console.error('위치 추적 오류 발생:', { + code: error.code, + message: error.message, + }); + }, + { + enableHighAccuracy: true, + distanceFilter: 0, + interval: 1000, + timeout: 5000, + }, + ); + + console.log('watchPosition 설정됨:', { watchId }); + + return () => { + console.log('위치 추적 정리(cleanup):', watchId); + Geolocation.clearWatch(watchId); + }; + }, [currentLocation, isWalking, lastUpdateTime]); // lastUpdateTime 의존성 추가 + + const handleLocationButtonPress = () => { + mapRef.current?.animateCameraTo({ + latitude: currentLocation.latitude, + longitude: currentLocation.longitude, + zoom: 18, + duration: 500, + easing: 'Fly', + }); + }; + const renderWalkButton = () => { if (!isWalking) { return setIsWalking(true)} bgColor="default" text="산책 시작" />; @@ -37,13 +204,35 @@ const MapView = () => { return ( <> + camera={camera} + > + + {locationMarkers.map((marker, index) => ( + + ))} + - {}} text="⊕ 내 위치로" bgColor="font_1" /> + {renderWalkButton()} From 7095773a4a471bc540a095ca61f1ce3c981b07b0 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 28 Jan 2025 19:13:20 +0900 Subject: [PATCH 04/45] =?UTF-8?q?feat:=20=ED=8A=B9=EC=A0=95=20=EA=B8=B0?= =?UTF-8?q?=EA=B0=84=EB=A7=88=EB=8B=A4=20=EB=A7=88=EC=BB=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 79 +++++++++++++++------------ 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 1935fb1..bf7ca3a 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -15,6 +15,20 @@ import * as S from './styles'; const WALKING_INTERVAL = 5000; const NORMAL_INTERVAL = 10000; const MIN_ACCURACY = 30; +const MIN_MARKER_DISTANCE = 5; + +const calculateDirectDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => { + const R = 6371e3; + const φ1 = (lat1 * Math.PI) / 180; + const φ2 = (lat2 * Math.PI) / 180; + const Δφ = ((lat2 - lat1) * Math.PI) / 180; + const Δλ = ((lon2 - lon1) * Math.PI) / 180; + + const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return R * c; +}; const MapView = () => { const mapRef = useRef(null); @@ -65,28 +79,41 @@ const MapView = () => { return position.coords.accuracy <= MIN_ACCURACY; }; + const shouldAddMarker = (newPosition: { latitude: number; longitude: number }): boolean => { + if (locationMarkers.length === 0) { + return true; + } + + const lastMarker = locationMarkers[locationMarkers.length - 1]; + const calDistance = calculateDirectDistance( + lastMarker.latitude, + lastMarker.longitude, + newPosition.latitude, + newPosition.longitude, + ); + + return calDistance >= MIN_MARKER_DISTANCE; + }; + useEffect(() => { console.log('위치 추적 시작:', { isWalking }); const requestLocationPermission = async () => { if (Platform.OS === 'ios') { const status = await request(PERMISSIONS.IOS.LOCATION_ALWAYS); - console.log('iOS 위치 권한 상태:', status); if (status === 'granted') { try { await requestLocationAccuracy({ purposeKey: 'common-purpose' }); - console.log('iOS 위치 정확도 설정 성공'); } catch (e) { console.error('iOS 위치 정확도 요청 실패:', e); } } } else { try { - const result = await requestMultiple([ + await requestMultiple([ PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION, ]); - console.log('Android 위치 권한 상태:', result); } catch (e) { console.error('Android 위치 권한 요청 실패:', e); } @@ -100,29 +127,11 @@ const MapView = () => { const currentTime = Date.now(); const timeSinceLastUpdate = currentTime - lastUpdateTime; - // 마지막 업데이트로부터 5초가 지나지 않았다면 무시 if (timeSinceLastUpdate < WALKING_INTERVAL) { - console.log('업데이트 무시: 시간 간격이 충분하지 않음', { - timeSinceLastUpdate, - requiredInterval: WALKING_INTERVAL, - }); return; } - console.log('위치 업데이트 받음:', { - accuracy: position.coords.accuracy, - latitude: position.coords.latitude, - longitude: position.coords.longitude, - timestamp: new Date(position.timestamp).toLocaleString(), - timeSinceLastUpdate, - isWalking, - }); - if (!filterPosition(position)) { - console.log('위치 정확도가 낮음:', { - accuracy: position.coords.accuracy, - required: MIN_ACCURACY, - }); return; } @@ -130,15 +139,16 @@ const MapView = () => { const newPosition = { latitude, longitude }; if (isWalking) { - setLocationMarkers(prev => [ - ...prev, - { - latitude, - longitude, - index: prev.length + 1, - }, - ]); - console.log('새 위치 마커 추가됨:', locationMarkers.length + 1); + if (shouldAddMarker(newPosition)) { + setLocationMarkers(prev => [ + ...prev, + { + latitude, + longitude, + index: prev.length + 1, + }, + ]); + } mapRef.current?.animateCameraTo({ latitude, @@ -154,10 +164,7 @@ const MapView = () => { setLastUpdateTime(currentTime); }, error => { - console.error('위치 추적 오류 발생:', { - code: error.code, - message: error.message, - }); + console.error('위치 추적 오류:', error); }, { enableHighAccuracy: true, @@ -173,7 +180,7 @@ const MapView = () => { console.log('위치 추적 정리(cleanup):', watchId); Geolocation.clearWatch(watchId); }; - }, [currentLocation, isWalking, lastUpdateTime]); // lastUpdateTime 의존성 추가 + }, [currentLocation, isWalking, lastUpdateTime]); const handleLocationButtonPress = () => { mapRef.current?.animateCameraTo({ From 85ae28f13a7472f9eaf716a81575b5a9a50c85ec Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 28 Jan 2025 19:28:33 +0900 Subject: [PATCH 05/45] =?UTF-8?q?fix:=20=EC=82=B0=EC=B1=85=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EC=8B=9C=20=ED=98=84=EC=9E=AC=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=20=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 55 ++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index bf7ca3a..eda4cb5 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -182,6 +182,51 @@ const MapView = () => { }; }, [currentLocation, isWalking, lastUpdateTime]); + useEffect(() => { + const getCurrentLocation = async () => { + try { + if (Platform.OS === 'ios') { + const status = await request(PERMISSIONS.IOS.LOCATION_ALWAYS); + if (status === 'granted') { + await requestLocationAccuracy({ purposeKey: 'common-purpose' }); + } + } else { + await requestMultiple([ + PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, + PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION, + ]); + } + + Geolocation.getCurrentPosition( + position => { + const { latitude, longitude } = position.coords; + + setCurrentLocation({ latitude, longitude }); + + mapRef.current?.animateCameraTo({ + latitude, + longitude, + zoom: 18, + duration: 500, + easing: 'Fly', + }); + }, + error => { + console.error('초기 위치 가져오기 실패:', error); + }, + { + enableHighAccuracy: true, + timeout: 5000, + }, + ); + } catch (error) { + console.error('위치 권한 요청 실패:', error); + } + }; + + getCurrentLocation(); + }, []); + const handleLocationButtonPress = () => { mapRef.current?.animateCameraTo({ latitude: currentLocation.latitude, @@ -216,7 +261,15 @@ const MapView = () => { isShowLocationButton={false} isShowZoomControls={false} isShowCompass={false} - camera={camera} + camera={ + currentLocation + ? { + latitude: currentLocation.latitude, + longitude: currentLocation.longitude, + zoom: 18, + } + : undefined + } > Date: Tue, 28 Jan 2025 20:07:51 +0900 Subject: [PATCH 06/45] =?UTF-8?q?feat:=20=EC=A4=91=EC=95=99=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=A9=80=EC=96=B4=EC=A7=88=20=EB=95=8C=20=EB=82=B4?= =?UTF-8?q?=20=EC=9C=84=EC=B9=98=EB=A1=9C=20=EB=B2=84=ED=8A=BC=20=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index eda4cb5..c47a4a9 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -65,6 +65,8 @@ const MapView = () => { const [lastUpdateTime, setLastUpdateTime] = useState(Date.now()); + const [isLocationCentered, setIsLocationCentered] = useState(true); + useEffect(() => { let interval: NodeJS.Timeout; if (isWalking) { @@ -253,6 +255,19 @@ const MapView = () => { ); }; + // 카메라 이동이 완료될 때마다 호출되는 함수 + const handleCameraChange = (event: any) => { + const { latitude, longitude } = event; + // console.log(latitude, longitude); + + const distance = calculateDirectDistance(latitude, longitude, currentLocation.latitude, currentLocation.longitude); + + console.log(distance); + + // 현재 위치와 카메라 중심점의 거리가 50미터 이상이면 중심에서 벗어난 것으로 판단 + setIsLocationCentered(distance < 20); + }; + return ( <> { isShowLocationButton={false} isShowZoomControls={false} isShowCompass={false} - camera={ - currentLocation - ? { - latitude: currentLocation.latitude, - longitude: currentLocation.longitude, - zoom: 18, - } - : undefined - } + camera={camera} + onCameraChanged={handleCameraChange} > { ))} - + {!isLocationCentered && ( + + )} {renderWalkButton()} From 9b308147f59bf5633cb6dc228f5b1fe2c7334306 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 28 Jan 2025 20:14:31 +0900 Subject: [PATCH 07/45] =?UTF-8?q?fix:=20=EC=82=B0=EC=B1=85=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=B0=8F=20=EA=B1=B0=EB=A6=AC=20=EB=8B=A8=EC=9C=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 2 +- src/screens/Home/WalkScreen.tsx | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index c47a4a9..8403f72 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -249,7 +249,7 @@ const MapView = () => { {formatDuration(walkTime)} setIsWalking(false)} bgColor="lighten_2" text="산책 끝" /> - {formatDistance(distance)}km + {formatDistance(distance)} ); diff --git a/src/screens/Home/WalkScreen.tsx b/src/screens/Home/WalkScreen.tsx index 3cde54d..e12d9a4 100644 --- a/src/screens/Home/WalkScreen.tsx +++ b/src/screens/Home/WalkScreen.tsx @@ -5,15 +5,22 @@ import MapView from '~components/Walk/MapView'; import WalkMessage from '~components/Walk/WalkMessage'; import { SafeAreaView, View } from 'react-native'; -export const formatDuration = (seconds: number) => { +export const formatDuration = (seconds: number): string => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; - return `${hours}시간 ${minutes}분`; + return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart( + 2, + '0', + )}`; }; -export const formatDistance = (meters: number) => { - return (meters / 1000).toFixed(1); +export const formatDistance = (meters: number): string => { + if (meters >= 1000) { + return `${(meters / 1000).toFixed(2)}km`; + } + return `${Math.round(meters)}m`; }; export const WalkScreen = () => { From 17772f119a5e9eaaaf8188d87c6f1a7fa2c2f7ba Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 31 Jan 2025 15:00:31 +0900 Subject: [PATCH 08/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=20=EC=8B=9C=EA=B0=84,=20=EA=B1=B0?= =?UTF-8?q?=EB=A6=AC=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 8403f72..4a470dc 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -13,7 +13,7 @@ import { formatDuration, formatDistance } from '~screens/Home/WalkScreen'; import * as S from './styles'; const WALKING_INTERVAL = 5000; -const NORMAL_INTERVAL = 10000; +// const NORMAL_INTERVAL = 10000; const MIN_ACCURACY = 30; const MIN_MARKER_DISTANCE = 5; @@ -34,7 +34,7 @@ const MapView = () => { const mapRef = useRef(null); const [isWalking, setIsWalking] = useState(false); const [walkTime, setWalkTime] = useState(0); - const [distance, _setDistance] = useState(0); + const [distance, setDistance] = useState(0); const [camera, _setCamera] = useState({ latitude: 37.50497126, @@ -182,6 +182,7 @@ const MapView = () => { console.log('위치 추적 정리(cleanup):', watchId); Geolocation.clearWatch(watchId); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentLocation, isWalking, lastUpdateTime]); useEffect(() => { @@ -248,7 +249,16 @@ const MapView = () => { {formatDuration(walkTime)} - setIsWalking(false)} bgColor="lighten_2" text="산책 끝" /> + { + setIsWalking(false); + setWalkTime(0); + setDistance(0); + setLocationMarkers([]); + }} + bgColor="lighten_2" + text="산책 끝" + /> {formatDistance(distance)} @@ -260,12 +270,17 @@ const MapView = () => { const { latitude, longitude } = event; // console.log(latitude, longitude); - const distance = calculateDirectDistance(latitude, longitude, currentLocation.latitude, currentLocation.longitude); + const calDistance = calculateDirectDistance( + latitude, + longitude, + currentLocation.latitude, + currentLocation.longitude, + ); - console.log(distance); + console.log(calDistance); // 현재 위치와 카메라 중심점의 거리가 50미터 이상이면 중심에서 벗어난 것으로 판단 - setIsLocationCentered(distance < 20); + setIsLocationCentered(calDistance < 20); }; return ( From f3db0e1fe9223ae6db02ee1cb088af8b58b77b3b Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 4 Feb 2025 14:45:17 +0900 Subject: [PATCH 09/45] =?UTF-8?q?feat:=20=EC=95=84=EB=B0=94=ED=83=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 4a470dc..9de08c4 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -6,7 +6,7 @@ import { NaverMapCircleOverlay, } from '@mj-studio/react-native-naver-map'; import { useEffect, useRef, useState } from 'react'; -import { Platform } from 'react-native'; +import { Image, Platform } from 'react-native'; import Geolocation from '@react-native-community/geolocation'; import { request, PERMISSIONS, requestLocationAccuracy, requestMultiple } from 'react-native-permissions'; import { formatDuration, formatDistance } from '~screens/Home/WalkScreen'; @@ -279,7 +279,7 @@ const MapView = () => { console.log(calDistance); - // 현재 위치와 카메라 중심점의 거리가 50미터 이상이면 중심에서 벗어난 것으로 판단 + // 현재 위치와 카메라 중심점의 거리가 20미터 이상이면 중심에서 벗어난 것으로 판단 setIsLocationCentered(calDistance < 20); }; @@ -298,8 +298,9 @@ const MapView = () => { latitude={currentLocation.latitude} longitude={currentLocation.longitude} anchor={{ x: 0.5, y: 1 }} - width={30} + width={40} height={40} + image={require('../../../assets/avatars/Avatar1.png')} /> {locationMarkers.map((marker, index) => ( Date: Tue, 4 Feb 2025 14:45:44 +0900 Subject: [PATCH 10/45] chore: install react-permission --- package-lock.json | 34 ++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index c3d4f86..6268a8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "ky": "^1.7.4", "react": "18.3.1", "react-native": "0.76.5", + "react-native-canvas": "^0.1.40", "react-native-dropdown-picker": "^5.4.6", "react-native-error-boundary": "^1.2.7", "react-native-linear-gradient": "^2.8.3", @@ -8038,6 +8039,12 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/ctx-polyfill": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/ctx-polyfill/-/ctx-polyfill-1.1.4.tgz", + "integrity": "sha512-tz65F3/zmZ2+CMtn4MhNhYi/yIN9dKnItMKzSkH2GE6dBpAIbUR0K5pSHWqUL3OuNBCA70DKlWZYUClHfydIXg==", + "license": "MIT" + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -14517,6 +14524,18 @@ } } }, + "node_modules/react-native-canvas": { + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/react-native-canvas/-/react-native-canvas-0.1.40.tgz", + "integrity": "sha512-VVQreUUGBYZF4qjCP09MoK8Eh5wr1LuL2XNKvQLcbd/NJ93Y8bT2pwwrZJXANCTVzAY2o/B9g+f0B7fS/vfXOA==", + "license": "MIT", + "dependencies": { + "ctx-polyfill": "^1.1.4" + }, + "peerDependencies": { + "react-native-webview": ">=5.10.0 || >=6.1.0" + } + }, "node_modules/react-native-dropdown-picker": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/react-native-dropdown-picker/-/react-native-dropdown-picker-5.4.6.tgz", @@ -14803,6 +14822,21 @@ "node": ">=10" } }, + "node_modules/react-native-webview": { + "version": "13.13.2", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.13.2.tgz", + "integrity": "sha512-zACPDTF0WnaEnKZ9mA/r/UpcOpV2gQM06AAIrOOexnO8UJvXL8Pjso0b/wTqKFxUZZnmjKuwd8gHVUosVOdVrw==", + "license": "MIT", + "peer": true, + "dependencies": { + "escape-string-regexp": "^4.0.0", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", diff --git a/package.json b/package.json index b7e80fd..5bb233c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "@emotion/react": "^11.14.0", "@mj-studio/react-native-naver-map": "^2.2.0", "@react-native-community/geolocation": "^3.4.0", - "react-native-permissions": "^5.2.1", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "@react-navigation/native-stack": "^7.2.0", @@ -27,6 +26,7 @@ "react-native-dropdown-picker": "^5.4.6", "react-native-error-boundary": "^1.2.7", "react-native-linear-gradient": "^2.8.3", + "react-native-permissions": "^5.2.1", "react-native-safe-area-context": "^5.0.0", "react-native-screens": "^4.4.0", "react-native-toast-message": "^2.2.1", From e2e3358c38eee52407b31d97a1d1634764be8e75 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 4 Feb 2025 14:46:06 +0900 Subject: [PATCH 11/45] =?UTF-8?q?chore:=20=EC=A7=80=EB=8F=84=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/DDang/Info.plist | 50 ++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/ios/DDang/Info.plist b/ios/DDang/Info.plist index 6336825..1d65e33 100644 --- a/ios/DDang/Info.plist +++ b/ios/DDang/Info.plist @@ -24,6 +24,8 @@ $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS + NMFClientId + j6vmx4vlbe NSAppTransportSecurity NSAllowsArbitraryLoads @@ -31,22 +33,12 @@ NSAllowsLocalNetworking + NSLocationAlwaysAndWhenInUseUsageDescription + 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. + NSLocationTemporaryUsageDescriptionDictionary + 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. NSLocationWhenInUseUsageDescription - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - arm64 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - + 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. UIAppFonts AntDesign.ttf @@ -78,13 +70,25 @@ SUIT-SemiBold.ttf SUIT-Thin.ttf - NMFClientId - j6vmx4vlbe - NSLocationAlwaysAndWhenInUseUsageDescription - 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. - NSLocationTemporaryUsageDescriptionDictionary - 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. - NSLocationWhenInUseUsageDescription - 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. + UIBackgroundModes + + fetch + processing + location + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + From d4f39ef0cc2dcab6c563635f2fbc276d8a4beb31 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 11 Feb 2025 13:12:18 +0900 Subject: [PATCH 12/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EC=A0=84=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Common/ListModal/index.tsx | 93 ++++++++++++++++------- src/components/Common/ListModal/styles.ts | 25 +++++- 2 files changed, 89 insertions(+), 29 deletions(-) diff --git a/src/components/Common/ListModal/index.tsx b/src/components/Common/ListModal/index.tsx index a77d4ef..b1cee97 100644 --- a/src/components/Common/ListModal/index.tsx +++ b/src/components/Common/ListModal/index.tsx @@ -1,37 +1,40 @@ import { Modal, ScrollView, Animated, Dimensions, GestureResponderEvent } from 'react-native'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Separator } from '~components/Common/Seperator'; import * as S from './styles'; +interface Dog { + dogId: number; + dogName: string; + breed: string; + dogBirthDate: string; + dogGender: string; + walkCount: number; + dogProfileImg: string; +} + interface DogListModalProps { isVisible: boolean; onClose: () => void; - dogs: { - id: string; - name: string; - breed: string; - age: string; - gender: string; - walkCount: number; - imageUrl: string; - }[]; - onSelectDog: (dog: { - id: string; - name: string; - breed: string; - age: string; - gender: string; - walkCount: number; - imageUrl: string; - }) => void; - type?: 'walk' | 'default'; + dogs: Dog[]; + onSelectDog: (dog: Dog) => void; + onSelectMultipleDogs?: (dogs: Dog[]) => void; + type?: 'walk' | 'default' | 'multi-select'; } const SCREEN_HEIGHT = Dimensions.get('window').height; const WALK_MODAL_HEIGHT = SCREEN_HEIGHT * 0.85; -export const DogListModal = ({ isVisible, onClose, dogs, onSelectDog, type = 'default' }: DogListModalProps) => { +export const DogListModal = ({ + isVisible, + onClose, + dogs, + onSelectDog, + onSelectMultipleDogs, + type = 'default', +}: DogListModalProps) => { const slideAnim = useRef(new Animated.Value(type === 'walk' ? WALK_MODAL_HEIGHT : 500)).current; + const [selectedDogs, setSelectedDogs] = useState([]); useEffect(() => { if (isVisible) { @@ -49,9 +52,25 @@ export const DogListModal = ({ isVisible, onClose, dogs, onSelectDog, type = 'de friction: 10, }).start(); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isVisible, type]); + const toggleDogSelection = (dog: Dog) => { + setSelectedDogs(prevSelected => { + if (prevSelected.some(selectedDog => selectedDog.dogId === dog.dogId)) { + return prevSelected.filter(selectedDog => selectedDog.dogId !== dog.dogId); + } else { + return [...prevSelected, dog]; + } + }); + }; + + const handleConfirmSelection = () => { + if (onSelectMultipleDogs) { + onSelectMultipleDogs(selectedDogs); + } + onClose(); + }; + return ( @@ -65,30 +84,48 @@ export const DogListModal = ({ isVisible, onClose, dogs, onSelectDog, type = 'de > {type === 'walk' ? '강번따 리스트' : '강아지 리스트'} + {type === 'multi-select' && ( + + 선택 완료 + + )} {dogs.map(dog => ( - + selectedDog.dogId === dog.dogId) + ? 'lightgray' + : 'white', + }} + > - {dog.name} + {dog.dogName} {dog.breed} - {dog.age} + {dog.dogBirthDate} - {dog.gender} + {dog.dogGender} 산책 횟수 {dog.walkCount}회 onSelectDog(dog)} + onPress={() => { + if (type === 'multi-select') { + toggleDogSelection(dog); + } else { + onSelectDog(dog); + } + }} type="roundedRect" bgColor="lighten_3" text={type === 'walk' ? '강번따' : '추가'} diff --git a/src/components/Common/ListModal/styles.ts b/src/components/Common/ListModal/styles.ts index a8ba627..733ec49 100644 --- a/src/components/Common/ListModal/styles.ts +++ b/src/components/Common/ListModal/styles.ts @@ -11,7 +11,7 @@ export const ModalBackground = styled.TouchableOpacity` background-color: rgba(0, 0, 0, 0.5); `; -export const ModalContainer = styled(Animated.View)<{ type: 'walk' | 'default' }>` +export const ModalContainer = styled(Animated.View)<{ type: 'walk' | 'default' | 'multi-select' }>` position: absolute; bottom: 0; left: 0; @@ -74,3 +74,26 @@ export const InfoContainer = styled.View` export const WalkText = styled(TextBold)``; export const SelectButton = styled(ResizeButton)``; + +interface ConfirmButtonProps { + bgColor?: string; +} + +export const ConfirmButton = styled.TouchableOpacity` + position: absolute; + top: 10px; + right: 10px; + background-color: ${({ theme, bgColor }) => (bgColor === 'default' ? theme.colors.default : theme.colors.lighten_3)}; + padding: 5px 10px; + border-radius: 5px; + align-items: center; + justify-content: center; + width: 70px; + height: 40px; +`; + +export const ConfirmButtonText = styled.Text` + color: ${({ theme }) => theme.colors.font_1}; + font-size: 14px; + font-weight: bold; +`; From f144b3eb36f77ccd5b9ae3e29be5102fc69f7abb Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 11 Feb 2025 13:16:01 +0900 Subject: [PATCH 13/45] =?UTF-8?q?feat:=EC=82=B0=EC=B1=85=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EC=A0=84=20=EA=B0=95=EC=95=84=EC=A7=80=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 33 +++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 9de08c4..d7ed8af 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -6,11 +6,13 @@ import { NaverMapCircleOverlay, } from '@mj-studio/react-native-naver-map'; import { useEffect, useRef, useState } from 'react'; -import { Image, Platform } from 'react-native'; +import { Platform } from 'react-native'; import Geolocation from '@react-native-community/geolocation'; import { request, PERMISSIONS, requestLocationAccuracy, requestMultiple } from 'react-native-permissions'; import { formatDuration, formatDistance } from '~screens/Home/WalkScreen'; import * as S from './styles'; +import { useMyDogInfo } from '~apis/dog/useMyDogInfo'; +import { DogListModal } from '~components/Common/ListModal'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; @@ -31,6 +33,10 @@ const calculateDirectDistance = (lat1: number, lon1: number, lat2: number, lon2: }; const MapView = () => { + const myDogInfo = useMyDogInfo(); + + // console.log(myDogInfo); + const mapRef = useRef(null); const [isWalking, setIsWalking] = useState(false); const [walkTime, setWalkTime] = useState(0); @@ -67,6 +73,8 @@ const MapView = () => { const [isLocationCentered, setIsLocationCentered] = useState(true); + const [isModalVisible, setIsModalVisible] = useState(false); + useEffect(() => { let interval: NodeJS.Timeout; if (isWalking) { @@ -240,9 +248,20 @@ const MapView = () => { }); }; + const handleStartWalkPress = () => { + setIsModalVisible(true); + }; + + const handleSelectDog = (dog: any) => { + console.log(dog); + setIsModalVisible(false); + setIsWalking(true); + // 선택된 강아지 정보를 사용하여 추가 로직을 구현할 수 있습니다. + }; + const renderWalkButton = () => { if (!isWalking) { - return setIsWalking(true)} bgColor="default" text="산책 시작" />; + return ; } return ( @@ -277,8 +296,6 @@ const MapView = () => { currentLocation.longitude, ); - console.log(calDistance); - // 현재 위치와 카메라 중심점의 거리가 20미터 이상이면 중심에서 벗어난 것으로 판단 setIsLocationCentered(calDistance < 20); }; @@ -321,6 +338,14 @@ const MapView = () => { )} {renderWalkButton()} + + setIsModalVisible(false)} + dogs={myDogInfo} // 강아지 목록을 전달합니다. + onSelectMultipleDogs={handleSelectDog} + type="multi-select" + /> ); }; From df26de99037bc476f05e34dcdebf6ccf4fb492bd Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 11 Feb 2025 14:03:53 +0900 Subject: [PATCH 14/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=ED=91=9C=EC=8B=9C=20=EB=B0=8F=20=EA=B1=B0=EB=A6=AC?= =?UTF-8?q?=20=EA=B3=84=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 68 +++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index d7ed8af..1499492 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -4,6 +4,7 @@ import { NaverMapView, NaverMapViewRef, NaverMapCircleOverlay, + NaverMapPolygonOverlay, } from '@mj-studio/react-native-naver-map'; import { useEffect, useRef, useState } from 'react'; import { Platform } from 'react-native'; @@ -13,6 +14,7 @@ import { formatDuration, formatDistance } from '~screens/Home/WalkScreen'; import * as S from './styles'; import { useMyDogInfo } from '~apis/dog/useMyDogInfo'; import { DogListModal } from '~components/Common/ListModal'; +import axios from 'axios'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; @@ -35,8 +37,6 @@ const calculateDirectDistance = (lat1: number, lon1: number, lat2: number, lon2: const MapView = () => { const myDogInfo = useMyDogInfo(); - // console.log(myDogInfo); - const mapRef = useRef(null); const [isWalking, setIsWalking] = useState(false); const [walkTime, setWalkTime] = useState(0); @@ -75,6 +75,8 @@ const MapView = () => { const [isModalVisible, setIsModalVisible] = useState(false); + const [routeCoordinates, setRouteCoordinates] = useState([]); + useEffect(() => { let interval: NodeJS.Timeout; if (isWalking) { @@ -300,6 +302,51 @@ const MapView = () => { setIsLocationCentered(calDistance < 20); }; + const fetchRouteData = async (coordinates: number[][]) => { + try { + const response = await axios.post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { + coordinates, + }); + const routeData = response.data; + const newRouteCoordinates = routeData.features[0].geometry.coordinates; + const routeDistance = routeData.features[0].properties.segments[0].distance; + + // 지도에 폴리곤 그리기 + setRouteCoordinates(newRouteCoordinates); + + // 거리 정보 업데이트 + setDistance(routeDistance); + } catch (error) { + console.error('경로 데이터 가져오기 실패:', error); + } + }; + + const drawRoutePolygon = () => { + if (isWalking && routeCoordinates.length >= 3) { + return ( + ({ latitude, longitude }))} + /> + ); + } + return null; + }; + + useEffect(() => { + if (isWalking && locationMarkers.length > 1) { + const lastMarker = locationMarkers[locationMarkers.length - 1]; + const secondLastMarker = locationMarkers[locationMarkers.length - 2]; + const newCoordinates = [ + [secondLastMarker.longitude, secondLastMarker.latitude], + [lastMarker.longitude, lastMarker.latitude], + ]; + fetchRouteData(newCoordinates); + } + }, [locationMarkers, isWalking]); + return ( <> { zIndex={2000 + index} /> ))} + {drawRoutePolygon()} {!isLocationCentered && ( @@ -339,13 +387,15 @@ const MapView = () => { {renderWalkButton()} - setIsModalVisible(false)} - dogs={myDogInfo} // 강아지 목록을 전달합니다. - onSelectMultipleDogs={handleSelectDog} - type="multi-select" - /> + {isModalVisible && ( + setIsModalVisible(false)} + dogs={myDogInfo} // 강아지 목록을 전달합니다. + onSelectDog={handleSelectDog} + type="multi-select" + /> + )} ); }; From abd6acfde838153553c566d29ce3625940636e9a Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 11 Feb 2025 14:07:55 +0900 Subject: [PATCH 15/45] =?UTF-8?q?fix:=20=EB=82=98=EC=9D=B4=EC=99=80=20?= =?UTF-8?q?=EC=84=B1=EB=B3=84=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Common/ListModal/index.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/Common/ListModal/index.tsx b/src/components/Common/ListModal/index.tsx index b1cee97..555a0f9 100644 --- a/src/components/Common/ListModal/index.tsx +++ b/src/components/Common/ListModal/index.tsx @@ -25,6 +25,19 @@ interface DogListModalProps { const SCREEN_HEIGHT = Dimensions.get('window').height; const WALK_MODAL_HEIGHT = SCREEN_HEIGHT * 0.85; +const calculateAge = (birthDate: string): number => { + const birth = new Date(birthDate); + const today = new Date(); + let age = today.getFullYear() - birth.getFullYear(); + const monthDiff = today.getMonth() - birth.getMonth(); + + if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) { + age--; + } + + return age; +}; + export const DogListModal = ({ isVisible, onClose, @@ -111,7 +124,7 @@ export const DogListModal = ({ {dog.breed} - {dog.dogBirthDate} + {calculateAge(dog.dogBirthDate)}살 {dog.dogGender} From 0b16fe6d1b516d67a5b1a7347978e28178ec29d6 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 11 Feb 2025 14:26:56 +0900 Subject: [PATCH 16/45] =?UTF-8?q?fix:=20=EC=9B=B9=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=EA=B5=AC=EB=8F=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useWebSocket.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts index c44e77b..b1b7dba 100644 --- a/src/hooks/useWebSocket.ts +++ b/src/hooks/useWebSocket.ts @@ -1,9 +1,9 @@ import { useEffect, useRef } from 'react'; import SockJS from 'sockjs-client'; -import { Client } from '@stomp/stompjs'; +import { Client, IMessage } from '@stomp/stompjs'; const token = - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsInByb3ZpZGVyIjoiR09PR0xFIiwiZXhwIjoxNzQwMjQ4MjA3LCJlbWFpbCI6ImJibGJibGFuNjlAZ21haWwuY29tIn0.CpauBw9_yXlYQjr-BZP7xqm1u63pj1g1aM3kX9HwCm37BMhpOQGz1Mq8R42CihtC8henTRy0OHaxa7q9-1Svzw'; + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsInByb3ZpZGVyIjoiS0FLQU8iLCJleHAiOjE3Mzk3NjQwMDIsImVtYWlsIjoibWtoNjc5M0BuYXZlci5jb20ifQ.EF03NpevMSZ2DcM5Q-trEEmRa0KEb5HpJ1HlD-Vj8xy3N2JoFvdQFoWDJRM3IGVwx58L9T2oV7GBTr6wJOevnA'; const SERVER_URL = 'https://ddang.site/ws'; @@ -43,5 +43,11 @@ export const useWebSocket = () => { } }; - return { client: stompClientRef.current, sendMessage }; + const subscribe = (destination: string, callback: (message: IMessage) => void) => { + if (stompClientRef.current && stompClientRef.current.connected) { + return stompClientRef.current.subscribe(destination, callback); + } + }; + + return { client: stompClientRef.current, sendMessage, subscribe }; }; From 1671182613c08df42e0883bc235125099e14b252 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Tue, 11 Feb 2025 14:27:14 +0900 Subject: [PATCH 17/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EC=A0=91?= =?UTF-8?q?=EC=86=8D=20=EC=8B=9C=20=EA=B5=AC=EB=8F=85=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 1499492..0eff97b 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -15,12 +15,15 @@ import * as S from './styles'; import { useMyDogInfo } from '~apis/dog/useMyDogInfo'; import { DogListModal } from '~components/Common/ListModal'; import axios from 'axios'; +import { useWebSocket } from '~hooks/useWebSocket'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; const MIN_ACCURACY = 30; const MIN_MARKER_DISTANCE = 5; +const USER_EMAIL = 'mkh6793@naver.com'; + const calculateDirectDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => { const R = 6371e3; const φ1 = (lat1 * Math.PI) / 180; @@ -77,6 +80,14 @@ const MapView = () => { const [routeCoordinates, setRouteCoordinates] = useState([]); + const { subscribe } = useWebSocket(); + + useEffect(() => { + subscribe(`/sub/walk/${USER_EMAIL}`, message => { + console.log(message); + }); + }, [subscribe]); + useEffect(() => { let interval: NodeJS.Timeout; if (isWalking) { From 8e8e1ae537170a60fafab804cea7966c88aabb4d Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 12 Feb 2025 12:45:58 +0900 Subject: [PATCH 18/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EA=B5=AC?= =?UTF-8?q?=EB=8F=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useWebSocket.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts index b1b7dba..83bd9a1 100644 --- a/src/hooks/useWebSocket.ts +++ b/src/hooks/useWebSocket.ts @@ -23,10 +23,9 @@ export const useWebSocket = () => { stompClient.onConnect = frame => { console.log('STOMP 연결 성공:', frame); - // 메시지 구독 예제 - // stompClient.subscribe('/sub/walk/bblbblan69@gmail.com', message => { - // console.log('받은 메시지:', message.body); - // }); + stompClient.subscribe('/sub/walk/mkh6793@naver.com', message => { + console.log('받은 메시지:', message.body); + }); }; stompClient.activate(); From a808cefeb2d18ec7ba334835c27e8ebb5199c006 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 12 Feb 2025 13:39:40 +0900 Subject: [PATCH 19/45] =?UTF-8?q?feat:=20=EC=8B=A4=EC=A0=9C=20=EB=8F=84?= =?UTF-8?q?=EB=A1=9C=20=EA=B8=B0=EB=B0=98=20=ED=8F=B4=EB=A6=AC=EA=B3=A4=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 57 +++++++++++---------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 0eff97b..7452845 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -4,7 +4,7 @@ import { NaverMapView, NaverMapViewRef, NaverMapCircleOverlay, - NaverMapPolygonOverlay, + NaverMapPathOverlay, } from '@mj-studio/react-native-naver-map'; import { useEffect, useRef, useState } from 'react'; import { Platform } from 'react-native'; @@ -15,7 +15,6 @@ import * as S from './styles'; import { useMyDogInfo } from '~apis/dog/useMyDogInfo'; import { DogListModal } from '~components/Common/ListModal'; import axios from 'axios'; -import { useWebSocket } from '~hooks/useWebSocket'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; @@ -80,14 +79,6 @@ const MapView = () => { const [routeCoordinates, setRouteCoordinates] = useState([]); - const { subscribe } = useWebSocket(); - - useEffect(() => { - subscribe(`/sub/walk/${USER_EMAIL}`, message => { - console.log(message); - }); - }, [subscribe]); - useEffect(() => { let interval: NodeJS.Timeout; if (isWalking) { @@ -119,8 +110,6 @@ const MapView = () => { }; useEffect(() => { - console.log('위치 추적 시작:', { isWalking }); - const requestLocationPermission = async () => { if (Platform.OS === 'ios') { const status = await request(PERMISSIONS.IOS.LOCATION_ALWAYS); @@ -196,11 +185,7 @@ const MapView = () => { timeout: 5000, }, ); - - console.log('watchPosition 설정됨:', { watchId }); - return () => { - console.log('위치 추적 정리(cleanup):', watchId); Geolocation.clearWatch(watchId); }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -300,7 +285,6 @@ const MapView = () => { // 카메라 이동이 완료될 때마다 호출되는 함수 const handleCameraChange = (event: any) => { const { latitude, longitude } = event; - // console.log(latitude, longitude); const calDistance = calculateDirectDistance( latitude, @@ -313,19 +297,27 @@ const MapView = () => { setIsLocationCentered(calDistance < 20); }; - const fetchRouteData = async (coordinates: number[][]) => { + // eslint-disable-next-line @typescript-eslint/no-shadow + const fetchRouteData = async (locationMarkers: { longitude: number; latitude: number }[]) => { + if (locationMarkers.length < 2) { + return; + } + + const lastTwoCoordinates = [ + [locationMarkers[locationMarkers.length - 2].longitude, locationMarkers[locationMarkers.length - 2].latitude], + [locationMarkers[locationMarkers.length - 1].longitude, locationMarkers[locationMarkers.length - 1].latitude], + ]; + try { const response = await axios.post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { - coordinates, + coordinates: lastTwoCoordinates, }); const routeData = response.data; const newRouteCoordinates = routeData.features[0].geometry.coordinates; const routeDistance = routeData.features[0].properties.segments[0].distance; - // 지도에 폴리곤 그리기 setRouteCoordinates(newRouteCoordinates); - // 거리 정보 업데이트 setDistance(routeDistance); } catch (error) { console.error('경로 데이터 가져오기 실패:', error); @@ -333,12 +325,12 @@ const MapView = () => { }; const drawRoutePolygon = () => { - if (isWalking && routeCoordinates.length >= 3) { + if (isWalking && routeCoordinates.length >= 2) { return ( - ({ latitude, longitude }))} /> ); @@ -347,14 +339,8 @@ const MapView = () => { }; useEffect(() => { - if (isWalking && locationMarkers.length > 1) { - const lastMarker = locationMarkers[locationMarkers.length - 1]; - const secondLastMarker = locationMarkers[locationMarkers.length - 2]; - const newCoordinates = [ - [secondLastMarker.longitude, secondLastMarker.latitude], - [lastMarker.longitude, lastMarker.latitude], - ]; - fetchRouteData(newCoordinates); + if (isWalking && locationMarkers.length >= 2) { + fetchRouteData(locationMarkers); } }, [locationMarkers, isWalking]); @@ -403,7 +389,8 @@ const MapView = () => { isVisible={isModalVisible} onClose={() => setIsModalVisible(false)} dogs={myDogInfo} // 강아지 목록을 전달합니다. - onSelectDog={handleSelectDog} + // onSelectDog={handleSelectDog} + onSelectMultipleDogs={handleSelectDog} type="multi-select" /> )} From f9b813bdc49348e37c90b76ab56d31242b3ad271 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 12 Feb 2025 13:47:20 +0900 Subject: [PATCH 20/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91,=20=EC=A2=85=EB=A3=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/walk/completeWalk.ts | 58 +++++++++++++++++++++++++++++++++++ src/apis/walk/startWalk.ts | 33 ++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/apis/walk/completeWalk.ts create mode 100644 src/apis/walk/startWalk.ts diff --git a/src/apis/walk/completeWalk.ts b/src/apis/walk/completeWalk.ts new file mode 100644 index 0000000..dfec6d8 --- /dev/null +++ b/src/apis/walk/completeWalk.ts @@ -0,0 +1,58 @@ +import { HTTPError } from 'ky'; +import { APIResponse } from '~types/api'; +import { api } from '~apis/api'; + +export interface RequestCompleteWalk { + request: { + totalDistanceMeter: number; + totalWalkTimeSecond: number; + }; + walkImgFile: string; +} + +export interface ResponseCompleteWalk { + code: string; + status: string; + message: string; + data: { + date: string; + memberName: string; + dogName: string; + totalDistanceMeter: number; + timeDuration: { + hours: number; + minutes: number; + seconds: number; + }; + totalCalorie: number; + walkImg: string; + walkWithDogInfo: { + dogId: number; + dogProfileImg: string; + dogName: string; + breed: string; + dogAge: number; + dogGender: string; + memberId: number; + }; + }; +} + +export const completeWalk = async (userInfo: RequestCompleteWalk): Promise> => { + try { + const response = api + .post('walk/complete', { + json: userInfo, + }) + .json>(); + return response; + } catch (error) { + if (error instanceof HTTPError) { + const errorData = await error.response.json(); + console.error(errorData); + } else { + console.error(error); + } + throw error; + } +}; diff --git a/src/apis/walk/startWalk.ts b/src/apis/walk/startWalk.ts new file mode 100644 index 0000000..4897024 --- /dev/null +++ b/src/apis/walk/startWalk.ts @@ -0,0 +1,33 @@ +import { HTTPError } from 'ky'; +import { APIResponse } from '~types/api'; +import { api } from '~apis/api'; + +export interface RequestStartWalk { + dogIds: number[]; +} + +export interface ResponseStartWalk { + code: string; + status: string; + message: string; + data: {} | string; +} + +export const startWalk = async (userInfo: RequestStartWalk): Promise> => { + try { + const response = api + .post('walk/start', { + json: userInfo, + }) + .json>(); + return response; + } catch (error) { + if (error instanceof HTTPError) { + const errorData = await error.response.json(); + console.error(errorData); + } else { + console.error(error); + } + throw error; + } +}; From 29e9b5245561cdf114fa37e093a3d57c00341ae0 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 12 Feb 2025 15:52:12 +0900 Subject: [PATCH 21/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20=ED=9B=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 97 ++++++++++++++++----------- 1 file changed, 57 insertions(+), 40 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 7452845..fe62206 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -16,6 +16,9 @@ import { useMyDogInfo } from '~apis/dog/useMyDogInfo'; import { DogListModal } from '~components/Common/ListModal'; import axios from 'axios'; +import ViewShot, { captureRef } from 'react-native-view-shot'; +import { CameraRoll } from '@react-native-camera-roll/camera-roll'; + const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; const MIN_ACCURACY = 30; @@ -79,6 +82,8 @@ const MapView = () => { const [routeCoordinates, setRouteCoordinates] = useState([]); + const viewShotRef = useRef(null); + useEffect(() => { let interval: NodeJS.Timeout; if (isWalking) { @@ -250,6 +255,25 @@ const MapView = () => { setIsModalVisible(true); }; + const handleStopWalkPress = async () => { + setIsWalking(false); + setWalkTime(0); + setDistance(0); + setLocationMarkers([]); + + try { + const uri = await captureRef(viewShotRef, { + format: 'png', + quality: 0.8, + }); + await CameraRoll.save(uri, { type: 'photo' }); + console.log('스크린샷 저장 성공:', uri); + console.log(uri); + } catch (error) { + console.error('산책 종료 이미지 저장 실패:', error); + } + }; + const handleSelectDog = (dog: any) => { console.log(dog); setIsModalVisible(false); @@ -266,16 +290,7 @@ const MapView = () => { {formatDuration(walkTime)} - { - setIsWalking(false); - setWalkTime(0); - setDistance(0); - setLocationMarkers([]); - }} - bgColor="lighten_2" - text="산책 끝" - /> + {formatDistance(distance)} @@ -346,37 +361,39 @@ const MapView = () => { return ( <> - - - {locationMarkers.map((marker, index) => ( - + + - ))} - {drawRoutePolygon()} - + {locationMarkers.map((marker, index) => ( + + ))} + {drawRoutePolygon()} + + {!isLocationCentered && ( From d03519189abc6dea74f1c34f478b021d9f96674b Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 14 Feb 2025 14:12:23 +0900 Subject: [PATCH 22/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=B0=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/WalkSummary/index.tsx | 58 ++++++++++++++++++ src/components/Walk/WalkSummary/styles.ts | 71 +++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/components/Walk/WalkSummary/index.tsx create mode 100644 src/components/Walk/WalkSummary/styles.ts diff --git a/src/components/Walk/WalkSummary/index.tsx b/src/components/Walk/WalkSummary/index.tsx new file mode 100644 index 0000000..6e22a53 --- /dev/null +++ b/src/components/Walk/WalkSummary/index.tsx @@ -0,0 +1,58 @@ +import { Text, Image } from 'react-native'; +import * as S from './styles'; +import { Icon } from '~components/Common/Icons'; + +interface WalkSummaryModalProps { + visible: boolean; + walkTime: number; + distance: number; + screenshotUri: string; + onClose: () => void; +} + +const WalkSummaryModal = ({ visible, walkTime, distance, screenshotUri, onClose }: WalkSummaryModalProps) => { + if (!visible) { + return null; + } + + console.log(walkTime, distance); + + const formattedDate = new Date().toISOString().split('T')[0].replace(/-/g, '.'); + + return ( + + + + + {formattedDate} + {'견주님과 밤톨이가'} + + {`${Math.floor(walkTime / 60)}분`} + {'동안 산책했어요.'} + + + + + + {`${Math.floor(walkTime / 3600)}:${Math.floor((walkTime % 3600) / 60)}:${ + walkTime % 60 + }`} + 산책 시간 + + + {`${(distance / 1000).toFixed(1)}km`} + 산책 거리 + + + 200kcal + 소모한 칼로리 + + + + + + + ); +}; + +export default WalkSummaryModal; diff --git a/src/components/Walk/WalkSummary/styles.ts b/src/components/Walk/WalkSummary/styles.ts new file mode 100644 index 0000000..bdec8e6 --- /dev/null +++ b/src/components/Walk/WalkSummary/styles.ts @@ -0,0 +1,71 @@ +import styled from '@emotion/native'; +import { Button } from 'react-native'; +import { BgBox } from '~components/Common/BgBox'; +import { TextBold, TextExtraBold, TextMedium } from '~components/Common/Text'; + +export const SafeAreaView = styled.SafeAreaView` + flex: 1; + justify-content: flex-end; + align-items: center; + pointer-events: box-none; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1000; + background-color: ${({ theme }) => theme.colors.lighten_3}; +`; + +export const Container = styled.View` + width: 100%; + height: 100%; + padding: 20px; + elevation: 5; +`; + +export const Content = styled.View` + align-items: center; +`; + +export const Header = styled.View` + width: 100%; + margin-bottom: 20px; + align-items: start; +`; + +export const Date = styled(TextBold)` + margin-bottom: 23px; + color: ${({ theme }) => theme.colors.font_1}; +`; + +export const Title = styled(TextExtraBold)` + margin-bottom: 10px; + color: ${({ theme }) => theme.colors.font_1}; +`; + +export const Minute = styled(TextExtraBold)` + color: ${({ theme }) => theme.colors.default}; +`; + +export const InfoContainer = styled(BgBox)` + margin-top: 25px; + margin-bottom: 20px; + flex-direction: row; + justify-content: space-around; + width: 100%; +`; + +export const InfoMain = styled(TextExtraBold)` + flex-direction: row; + align-items: center; +`; + +export const InfoSub = styled(TextMedium)` + flex-direction: row; + align-items: center; +`; + +export const InfoItem = styled.View` + align-items: center; // 중앙 정렬 +`; From 92c0091d115e18b369c1b98cbb711bd2a77be178 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 14 Feb 2025 14:17:57 +0900 Subject: [PATCH 23/45] chore: install camera-roll --- package-lock.json | 196 ++++++++++++++++++++++++++++++++++++++++------ package.json | 3 + 2 files changed, 177 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0bbaa39..b8aedec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,13 @@ "@emotion/native": "^11.11.0", "@emotion/react": "^11.14.0", "@mj-studio/react-native-naver-map": "^2.2.0", + "@react-native-camera-roll/camera-roll": "^7.9.0", "@react-native-community/geolocation": "^3.4.0", "@react-native-community/netinfo": "^11.4.1", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "@react-navigation/native-stack": "^7.2.0", + "@react-navigation/stack": "^7.1.1", "@stomp/stompjs": "^7.0.0", "@tanstack/react-query": "^5.63.0", "d3": "^7.9.0", @@ -39,6 +41,7 @@ "react-native-toast-message": "^2.2.1", "react-native-url-polyfill": "^2.0.0", "react-native-vector-icons": "^10.2.0", + "react-native-view-shot": "^4.0.3", "react-native-webview": "^13.13.2", "sockjs-client": "^1.6.1" }, @@ -2159,7 +2162,6 @@ "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", - "dev": true, "dependencies": { "@types/hammerjs": "^2.0.36" }, @@ -3648,6 +3650,18 @@ "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, + "node_modules/@react-native-camera-roll/camera-roll": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@react-native-camera-roll/camera-roll/-/camera-roll-7.9.0.tgz", + "integrity": "sha512-Ra0lB1G2H11MzL5aIH3bwlxU1zaHGSZeRs4lBXLBO64Ai1gUgZPR7TYgKDeeRPzNPtSbZKmzs+fuZ/7XoCf1SA==", + "license": "MIT", + "engines": { + "node": ">= 18.17.0" + }, + "peerDependencies": { + "react-native": ">=0.59" + } + }, "node_modules/@react-native-community/cli": { "version": "15.0.1", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-15.0.1.tgz", @@ -4333,6 +4347,24 @@ "nanoid": "3.3.8" } }, + "node_modules/@react-navigation/stack": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-7.1.1.tgz", + "integrity": "sha512-CBTKQlIkELp05zRiTAv5Pa7OMuCpKyBXcdB3PGMN2Mm55/5MkDsA1IaZorp/6TsVCdllITD6aTbGX/HA/88A6w==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.2.5", + "color": "^4.2.3" + }, + "peerDependencies": { + "@react-navigation/native": "^7.0.14", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -5556,8 +5588,7 @@ "node_modules/@types/hammerjs": { "version": "2.0.46", "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", - "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", - "dev": true + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -6729,6 +6760,12 @@ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -6744,6 +6781,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -7013,6 +7061,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -7595,6 +7652,18 @@ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", @@ -7804,6 +7873,15 @@ "node": ">=4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -8280,12 +8358,6 @@ "node": ">=12" } }, - "node_modules/ctx-polyfill": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/ctx-polyfill/-/ctx-polyfill-1.1.4.tgz", - "integrity": "sha512-tz65F3/zmZ2+CMtn4MhNhYi/yIN9dKnItMKzSkH2GE6dBpAIbUR0K5pSHWqUL3OuNBCA70DKlWZYUClHfydIXg==", - "license": "MIT" - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -8488,6 +8560,15 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9941,6 +10022,26 @@ "node": ">=0.4.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.4.tgz", @@ -9956,6 +10057,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -10389,6 +10504,19 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -14077,6 +14205,12 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -14333,18 +14467,6 @@ "react-native": ">= 0.64.3" } }, - "node_modules/react-native-canvas": { - "version": "0.1.40", - "resolved": "https://registry.npmjs.org/react-native-canvas/-/react-native-canvas-0.1.40.tgz", - "integrity": "sha512-VVQreUUGBYZF4qjCP09MoK8Eh5wr1LuL2XNKvQLcbd/NJ93Y8bT2pwwrZJXANCTVzAY2o/B9g+f0B7fS/vfXOA==", - "license": "MIT", - "dependencies": { - "ctx-polyfill": "^1.1.4" - }, - "peerDependencies": { - "react-native-webview": ">=5.10.0 || >=6.1.0" - } - }, "node_modules/react-native-dropdown-picker": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/react-native-dropdown-picker/-/react-native-dropdown-picker-5.4.6.tgz", @@ -14383,7 +14505,6 @@ "version": "2.23.0", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.23.0.tgz", "integrity": "sha512-xtkdIU4S4uc4J2WO4hy7AXxD/1M8Be2yOrLdPTuWKAOF3KyL0D0xSdvuaWhI+GdZCNQQisj9kvbnMQGGb9XZNQ==", - "dev": true, "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -14649,6 +14770,19 @@ "node": ">=10" } }, + "node_modules/react-native-view-shot": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-4.0.3.tgz", + "integrity": "sha512-USNjYmED7C0me02c1DxKA0074Hw+y/nxo+xJKlffMvfUWWzL5ELh/TJA/pTnVqFurIrzthZDPtDM7aBFJuhrHQ==", + "license": "MIT", + "dependencies": { + "html2canvas": "^1.4.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-webview": { "version": "13.13.2", "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.13.2.tgz", @@ -16303,6 +16437,15 @@ "node": "*" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -16711,6 +16854,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", diff --git a/package.json b/package.json index c76e5e7..c2b5908 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,13 @@ "@emotion/native": "^11.11.0", "@emotion/react": "^11.14.0", "@mj-studio/react-native-naver-map": "^2.2.0", + "@react-native-camera-roll/camera-roll": "^7.9.0", "@react-native-community/geolocation": "^3.4.0", "@react-native-community/netinfo": "^11.4.1", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "@react-navigation/native-stack": "^7.2.0", + "@react-navigation/stack": "^7.1.1", "@stomp/stompjs": "^7.0.0", "@tanstack/react-query": "^5.63.0", "d3": "^7.9.0", @@ -42,6 +44,7 @@ "react-native-toast-message": "^2.2.1", "react-native-url-polyfill": "^2.0.0", "react-native-vector-icons": "^10.2.0", + "react-native-view-shot": "^4.0.3", "react-native-webview": "^13.13.2", "sockjs-client": "^1.6.1" }, From f7ea595e40fec0c166d57ec2602eb3f390479dd7 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 14 Feb 2025 14:18:35 +0900 Subject: [PATCH 24/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EB=A7=88?= =?UTF-8?q?=EB=AC=B4=EB=A6=AC=20svg=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Common/Icons/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Common/Icons/index.tsx b/src/components/Common/Icons/index.tsx index 069e0db..24eb017 100644 --- a/src/components/Common/Icons/index.tsx +++ b/src/components/Common/Icons/index.tsx @@ -33,6 +33,7 @@ import FamilyInvitationGuide from '~assets/family-invitation-guide.svg'; import FamilyJoinGuide from '~assets/family-join-guide.svg'; import FamilyJoinGuide2 from '~assets/family-join-guide2.svg'; import FamilyJoinGuide3 from '~assets/family-join-guide3.svg'; +import WalkSummary from '~assets/walk-summary.svg'; export const Icon = { Bell: (props: SvgProps) => , @@ -69,4 +70,5 @@ export const Icon = { FamilyJoinGuide: (props: SvgProps) => , FamilyJoinGuide2: (props: SvgProps) => , FamilyJoinGuide3: (props: SvgProps) => , + WalkSummary: (props: SvgProps) => , }; From d074e00bad0023cd74519dc086bb91fd0dc0bfd5 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 14 Feb 2025 14:18:41 +0900 Subject: [PATCH 25/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EB=A7=88?= =?UTF-8?q?=EB=AC=B4=EB=A6=AC=20svg=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/walk-summary.svg | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/assets/walk-summary.svg diff --git a/src/assets/walk-summary.svg b/src/assets/walk-summary.svg new file mode 100644 index 0000000..444d580 --- /dev/null +++ b/src/assets/walk-summary.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + From 461e4687e41c44745a6183910ae7c35e268d613a Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 14 Feb 2025 14:22:59 +0900 Subject: [PATCH 26/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 71 +++++++++++++++++++-------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index fe62206..81201e4 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -14,15 +14,16 @@ import { formatDuration, formatDistance } from '~screens/Home/WalkScreen'; import * as S from './styles'; import { useMyDogInfo } from '~apis/dog/useMyDogInfo'; import { DogListModal } from '~components/Common/ListModal'; -import axios from 'axios'; +import ky from 'ky'; import ViewShot, { captureRef } from 'react-native-view-shot'; import { CameraRoll } from '@react-native-camera-roll/camera-roll'; +import WalkSummaryModal from '../WalkSummary'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; const MIN_ACCURACY = 30; -const MIN_MARKER_DISTANCE = 5; +const MIN_MARKER_DISTANCE = 20; const USER_EMAIL = 'mkh6793@naver.com'; @@ -75,14 +76,12 @@ const MapView = () => { >([]); const [lastUpdateTime, setLastUpdateTime] = useState(Date.now()); - const [isLocationCentered, setIsLocationCentered] = useState(true); - const [isModalVisible, setIsModalVisible] = useState(false); - const [routeCoordinates, setRouteCoordinates] = useState([]); - const viewShotRef = useRef(null); + const [screenshotUri, setScreenshotUri] = useState(''); + const [isWalkSummaryVisible, setIsWalkSummaryVisible] = useState(false); useEffect(() => { let interval: NodeJS.Timeout; @@ -257,9 +256,6 @@ const MapView = () => { const handleStopWalkPress = async () => { setIsWalking(false); - setWalkTime(0); - setDistance(0); - setLocationMarkers([]); try { const uri = await captureRef(viewShotRef, { @@ -267,18 +263,30 @@ const MapView = () => { quality: 0.8, }); await CameraRoll.save(uri, { type: 'photo' }); - console.log('스크린샷 저장 성공:', uri); + + console.log(walkTime); + console.log(distance); console.log(uri); + + setScreenshotUri(uri); + setIsWalkSummaryVisible(true); } catch (error) { console.error('산책 종료 이미지 저장 실패:', error); } }; + // WalkSummaryModal이 닫힐 때 호출되는 함수 + const handleWalkSummaryClose = () => { + setWalkTime(0); + setDistance(0); + setLocationMarkers([]); + setIsWalkSummaryVisible(false); + }; + const handleSelectDog = (dog: any) => { console.log(dog); setIsModalVisible(false); setIsWalking(true); - // 선택된 강아지 정보를 사용하여 추가 로직을 구현할 수 있습니다. }; const renderWalkButton = () => { @@ -297,7 +305,6 @@ const MapView = () => { ); }; - // 카메라 이동이 완료될 때마다 호출되는 함수 const handleCameraChange = (event: any) => { const { latitude, longitude } = event; @@ -308,7 +315,6 @@ const MapView = () => { currentLocation.longitude, ); - // 현재 위치와 카메라 중심점의 거리가 20미터 이상이면 중심에서 벗어난 것으로 판단 setIsLocationCentered(calDistance < 20); }; @@ -324,15 +330,33 @@ const MapView = () => { ]; try { - const response = await axios.post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { - coordinates: lastTwoCoordinates, - }); - const routeData = response.data; + const response = await ky + .post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { + json: { + coordinates: lastTwoCoordinates, + }, + }) + .json(); + const routeData = response as { + features: [ + { + geometry: { + coordinates: number[][]; + }; + properties: { + segments: [ + { + distance: number; + }, + ]; + }; + }, + ]; + }; const newRouteCoordinates = routeData.features[0].geometry.coordinates; const routeDistance = routeData.features[0].properties.segments[0].distance; setRouteCoordinates(newRouteCoordinates); - setDistance(routeDistance); } catch (error) { console.error('경로 데이터 가져오기 실패:', error); @@ -405,12 +429,19 @@ const MapView = () => { setIsModalVisible(false)} - dogs={myDogInfo} // 강아지 목록을 전달합니다. - // onSelectDog={handleSelectDog} + dogs={myDogInfo} onSelectMultipleDogs={handleSelectDog} type="multi-select" /> )} + + ); }; From ab40b29b97bf3531860151a3ad7dbaf649995283 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Sat, 15 Feb 2025 00:35:21 +0900 Subject: [PATCH 27/45] =?UTF-8?q?feat:=20=EC=82=B0=EC=B1=85=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91,=20=EC=A2=85=EB=A3=8C=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 81201e4..26c96a7 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -19,6 +19,8 @@ import ky from 'ky'; import ViewShot, { captureRef } from 'react-native-view-shot'; import { CameraRoll } from '@react-native-camera-roll/camera-roll'; import WalkSummaryModal from '../WalkSummary'; +import { startWalk } from '~apis/walk/startWalk'; +import { completeWalk } from '~apis/walk/completeWalk'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; @@ -262,13 +264,23 @@ const MapView = () => { format: 'png', quality: 0.8, }); - await CameraRoll.save(uri, { type: 'photo' }); + const savedUri = await CameraRoll.save(uri, { type: 'photo' }); console.log(walkTime); console.log(distance); console.log(uri); + console.log(savedUri); + + const response = await completeWalk({ + request: { + totalDistanceMeter: distance, + totalWalkTimeSecond: walkTime, + }, + walkImgFile: savedUri, + }); + console.log('산책 완료 API 호출 성공:', response); - setScreenshotUri(uri); + setScreenshotUri(savedUri); setIsWalkSummaryVisible(true); } catch (error) { console.error('산책 종료 이미지 저장 실패:', error); @@ -283,10 +295,18 @@ const MapView = () => { setIsWalkSummaryVisible(false); }; - const handleSelectDog = (dog: any) => { + const handleSelectDog = async (dog: any) => { console.log(dog); setIsModalVisible(false); setIsWalking(true); + + try { + const dogIds = dog.map((d: any) => d.dogId); + const response = await startWalk({ dogIds }); + console.log(response); + } catch (error) { + console.error('산책 시작 실패:', error); + } }; const renderWalkButton = () => { From 313539fb3d749fc8a430a868557f594b57024e71 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Sat, 15 Feb 2025 00:53:24 +0900 Subject: [PATCH 28/45] =?UTF-8?q?feat:=20=ED=98=BC=EC=9E=90=20=EC=82=B0?= =?UTF-8?q?=EC=B1=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 26c96a7..71d4b50 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -21,6 +21,7 @@ import { CameraRoll } from '@react-native-camera-roll/camera-roll'; import WalkSummaryModal from '../WalkSummary'; import { startWalk } from '~apis/walk/startWalk'; import { completeWalk } from '~apis/walk/completeWalk'; +import { useWebSocket } from '~hooks/useWebSocket'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; @@ -44,6 +45,9 @@ const calculateDirectDistance = (lat1: number, lon1: number, lat2: number, lon2: const MapView = () => { const myDogInfo = useMyDogInfo(); + const { sendMessage, responseData } = useWebSocket(); + + console.log(responseData); const mapRef = useRef(null); const [isWalking, setIsWalking] = useState(false); @@ -378,6 +382,13 @@ const MapView = () => { setRouteCoordinates(newRouteCoordinates); setDistance(routeDistance); + + const lastCoordinate = newRouteCoordinates[newRouteCoordinates.length - 1]; + const message = JSON.stringify({ + latitude: lastCoordinate[1], + longitude: lastCoordinate[0], + }); + sendMessage('/pub/api/v1/walk-alone', message); } catch (error) { console.error('경로 데이터 가져오기 실패:', error); } From 5e13a0b760c738f32766c52922a0be65b37dcc44 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 19 Feb 2025 14:31:15 +0900 Subject: [PATCH 29/45] =?UTF-8?q?feat:=20=EC=9B=B9=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useWebSocket.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts index 83bd9a1..aa81c3b 100644 --- a/src/hooks/useWebSocket.ts +++ b/src/hooks/useWebSocket.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; import SockJS from 'sockjs-client'; import { Client, IMessage } from '@stomp/stompjs'; @@ -9,6 +9,7 @@ const SERVER_URL = 'https://ddang.site/ws'; export const useWebSocket = () => { const stompClientRef = useRef(null); + const [responseData, setResponseData] = useState(null); useEffect(() => { const stompClient = new Client({ @@ -24,7 +25,9 @@ export const useWebSocket = () => { console.log('STOMP 연결 성공:', frame); stompClient.subscribe('/sub/walk/mkh6793@naver.com', message => { - console.log('받은 메시지:', message.body); + const response = JSON.parse(message.body); + console.log('받은 메시지:', response); + setResponseData(response); }); }; @@ -48,5 +51,5 @@ export const useWebSocket = () => { } }; - return { client: stompClientRef.current, sendMessage, subscribe }; + return { client: stompClientRef.current, sendMessage, subscribe, responseData }; }; From 472c3c58b09b8d2dbec3b78f680369e214e4ab2c Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 19 Feb 2025 14:31:25 +0900 Subject: [PATCH 30/45] =?UTF-8?q?feat:=20=EC=95=84=EB=B0=94=ED=83=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/avatars/Avatar1.png | Bin 0 -> 2070 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/assets/avatars/Avatar1.png diff --git a/src/assets/avatars/Avatar1.png b/src/assets/avatars/Avatar1.png new file mode 100644 index 0000000000000000000000000000000000000000..a567eec02072e39135fc95c7ca9c1eae3a1aa426 GIT binary patch literal 2070 zcmV+x2K4A52tlL8v8#>$|^py3A?O*Aoz8jXn>ToMzEOZ!9Qk5Z$?pwU(n zja#f+u!_o52^f}kYM^Clp{2`Iy3KTk&h)i2<2i5Md%S600ulB3=9t8kCwl2q}5Gz&+Mst=%wpMM@Jj6P=a}Rd3$E7cH{Ny z@$?gqidQ}O!}T{l`|=wcIo=jez+k8!8ykBbdI+*f>Bc6iI^{lAXxHmIbrVZO^vyl* z&Q{b-LTn*cP}MD zszagBr$|MR%}ST8a+}Th;fLH`&pi1U^kRH?2 z&I_XH^G}qpkqEzk*FdEasc`4Lk5bv5bPN;R_RwR>!cnhmBmZIbWO9t1FFc!+TyQd= zi5w$DV^e{jwd=K=N}EF)M-qh~?-KHlEcqSInt3PdIDQZxy|DvYvEtfwn#S>S)gg>H ziSgGgF?Py7uc>{w$jn&)u_0yfW^qj`>rD)eb%2gNF=uJ@!F|+)-S-RMRA{y!rvcejf_*;>UM* zhEbZ6sgU#qE}Ve!0)UHzXe5I0WDwWnWx-^zpnuGZ ziOCQubF(0XqtJtrcn&!nW|VVYve>7PF&H42jPazY57ZZ(o`x_JLFt+`C@(LEFfuCk z>%lAzIc6*X6IKfr3DZamhhdIHnIX(JoA_Jr$DhKQor4JNRC%}GhM>uWD*+SaGnl9% zZ#i023>ytZGPb`Kdi^k}NGyBse$4dr!Bj{tu%rNjS)YP_f85UFHA)0U^m{zei)lL7 zu@A~H+U-bRa}5#9M<~rKx`ZAK#B=Cjg34Lv^qDT`!NuWG^m)e-CUb^EQ;3ks8Quv$ zy8V6x($k<9oX4op%W<$pZguJ)c01ou$;}DH4q+!=lOfN5_@M)djCpn=~nYf3)Mu^LgPOvtT)5-Ng&ybkZ z$mko7qWKhXperhpn}}_-ux93CW;zJ}#UA*{{{&LZLo535scsAn{f9}*GT0VZieo1Q zdfFJ!+8=wtY}#nDZF7m%?S6|ENsJ)z`JRscKgcnlUXhp-}Dk_!;LPiCa zEnk5EZE3|TS8zHA4?SQ-5RsipVd?6%>3nnucCr{{-TN9KyyV9- zFTJe9K>jo`K0Yp{phgwrT1YXGWp@4404*q4>qPh9Br0i>ub?tG8pM){n>5|a?sB{@ ziuXQhjPpHNJ%+Lcu!`i1Q^bC=_=Xoc#EhzFRq8JZC601z+OP~3%HG{w+PEIoOKqY{mJoqh|>K{k%$%B{=K_Lt+7@`+$+VeP0(3~o`_~TKc@d?jD zp&0h6&CPFpuKiE(0Tx>(vKN=d-E;HmJlHHI96LKsyGSG=vuFZ z7#2cM4NmwurpF07*qoM6N<$g7BQ) AmjD0& literal 0 HcmV?d00001 From 3f354d761fce12d6def24b55b4ec892ade9305de Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 19 Feb 2025 14:33:13 +0900 Subject: [PATCH 31/45] =?UTF-8?q?fix:=20api=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 64 ++++++++++++++++----------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 71d4b50..b1532a4 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -14,7 +14,8 @@ import { formatDuration, formatDistance } from '~screens/Home/WalkScreen'; import * as S from './styles'; import { useMyDogInfo } from '~apis/dog/useMyDogInfo'; import { DogListModal } from '~components/Common/ListModal'; -import ky from 'ky'; +// import ky from 'ky'; +import axios from 'axios'; import ViewShot, { captureRef } from 'react-native-view-shot'; import { CameraRoll } from '@react-native-camera-roll/camera-roll'; @@ -26,7 +27,7 @@ import { useWebSocket } from '~hooks/useWebSocket'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; const MIN_ACCURACY = 30; -const MIN_MARKER_DISTANCE = 20; +const MIN_MARKER_DISTANCE = 5; const USER_EMAIL = 'mkh6793@naver.com'; @@ -47,7 +48,7 @@ const MapView = () => { const myDogInfo = useMyDogInfo(); const { sendMessage, responseData } = useWebSocket(); - console.log(responseData); + console.log('받은 데이터 : ' + responseData); const mapRef = useRef(null); const [isWalking, setIsWalking] = useState(false); @@ -353,30 +354,38 @@ const MapView = () => { [locationMarkers[locationMarkers.length - 1].longitude, locationMarkers[locationMarkers.length - 1].latitude], ]; + console.log('lastTwoCoordinates : ' + lastTwoCoordinates); + console.log('lastTwoCoordinates : ' + typeof lastTwoCoordinates); + try { - const response = await ky - .post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { - json: { - coordinates: lastTwoCoordinates, - }, - }) - .json(); - const routeData = response as { - features: [ - { - geometry: { - coordinates: number[][]; - }; - properties: { - segments: [ - { - distance: number; - }, - ]; - }; - }, - ]; - }; + // const response = await ky + // .post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { + // json: { + // coordinates: lastTwoCoordinates, + // }, + // }) + // .json(); + // const routeData = response as { + // features: [ + // { + // geometry: { + // coordinates: number[][]; + // }; + // properties: { + // segments: [ + // { + // distance: number; + // }, + // ]; + // }; + // }, + // ]; + // }; + + const response = await axios.post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { + coordinates: lastTwoCoordinates, + }); + const routeData = response.data; const newRouteCoordinates = routeData.features[0].geometry.coordinates; const routeDistance = routeData.features[0].properties.segments[0].distance; @@ -388,7 +397,8 @@ const MapView = () => { latitude: lastCoordinate[1], longitude: lastCoordinate[0], }); - sendMessage('/pub/api/v1/walk-alone', message); + // sendMessage('/pub/api/v1/walk-alone', message); + console.log('메시지 보내기 완료'); } catch (error) { console.error('경로 데이터 가져오기 실패:', error); } From 6eff7c2a3783d959c79ac92a1f2d189de149591c Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 19 Feb 2025 14:47:32 +0900 Subject: [PATCH 32/45] =?UTF-8?q?fix:=20Info.plist=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/DDang/Info.plist | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/DDang/Info.plist b/ios/DDang/Info.plist index 52e8fda..9ab5b43 100644 --- a/ios/DDang/Info.plist +++ b/ios/DDang/Info.plist @@ -80,7 +80,6 @@ UIViewControllerBasedStatusBarAppearance - 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. UIAppFonts AntDesign.ttf From 643b69f4f51bd44ab6110555c71eae03fa82a0ea Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Thu, 20 Feb 2025 15:58:55 +0900 Subject: [PATCH 33/45] =?UTF-8?q?fix:=20WebSocketProvider=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 14 +++++--------- src/providers/AppProviders.tsx | 5 ++++- src/providers/WebSocketProvider.tsx | 15 +++++---------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a1ad81f..a804154 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,16 +20,12 @@ const navTheme = { }; const MainApp = () => { - const { client, sendMessage } = useWebSocket(); - return ( - - - - - - - + + + + + ); }; diff --git a/src/providers/AppProviders.tsx b/src/providers/AppProviders.tsx index ecca4ed..f77046a 100644 --- a/src/providers/AppProviders.tsx +++ b/src/providers/AppProviders.tsx @@ -2,12 +2,15 @@ import { PropsWithChildren } from 'react'; import { EmotionProvider } from '~providers/EmotionProvider'; import { TanstackQueryProvider } from '~providers/QueryClientProvider'; import { ToastProvider } from '~providers/ToastProvider'; +import { WebSocketProvider } from '~providers/WebSocketProvider'; export const AppProviders = ({ children }: PropsWithChildren) => { return ( - {children} + + {children} + ); diff --git a/src/providers/WebSocketProvider.tsx b/src/providers/WebSocketProvider.tsx index af85047..afd1f28 100644 --- a/src/providers/WebSocketProvider.tsx +++ b/src/providers/WebSocketProvider.tsx @@ -1,28 +1,23 @@ import { Client } from '@stomp/stompjs'; import { createContext, useContext, PropsWithChildren } from 'react'; +import { useWebSocket } from '~hooks/useWebSocket'; type WebSocketContextType = { client: Client | null; sendMessage: (destination: string, body: any) => void; }; -const WebSocketContext = createContext({ - client: null, - sendMessage: () => {}, // 기본 빈 함수 -}); +const WebSocketContext = createContext(undefined); -type WebSocketProviderProps = { - client: Client | null; - sendMessage: (destination: string, body: any) => void; -}; +export const WebSocketProvider = ({ children }: PropsWithChildren) => { + const { client, sendMessage } = useWebSocket(); -export const WebSocketProvider = ({ children, client, sendMessage }: PropsWithChildren) => { return {children}; }; export const useWebSocketContext = () => { const context = useContext(WebSocketContext); - if (context === undefined) { + if (!context) { throw new Error('useWebSocketContext must be used within a WebSocketProvider'); } return context; From cfbcc1e713c7b46b521a198ff103a5ae58d4578e Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Wed, 26 Feb 2025 15:33:15 +0900 Subject: [PATCH 34/45] =?UTF-8?q?fix:=20=EA=B2=BD=EB=A1=9C=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index b1532a4..9aaa1be 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -27,9 +27,7 @@ import { useWebSocket } from '~hooks/useWebSocket'; const WALKING_INTERVAL = 5000; // const NORMAL_INTERVAL = 10000; const MIN_ACCURACY = 30; -const MIN_MARKER_DISTANCE = 5; - -const USER_EMAIL = 'mkh6793@naver.com'; +const MIN_MARKER_DISTANCE = 1; const calculateDirectDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => { const R = 6371e3; @@ -307,7 +305,7 @@ const MapView = () => { try { const dogIds = dog.map((d: any) => d.dogId); - const response = await startWalk({ dogIds }); + // const response = await startWalk({ dogIds }); console.log(response); } catch (error) { console.error('산책 시작 실패:', error); @@ -382,13 +380,15 @@ const MapView = () => { // ]; // }; - const response = await axios.post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { + const response = await axios.post('https://ruehan-home.com:8004/ors/v2/directions/foot-walking/geojson', { coordinates: lastTwoCoordinates, }); const routeData = response.data; const newRouteCoordinates = routeData.features[0].geometry.coordinates; const routeDistance = routeData.features[0].properties.segments[0].distance; + console.log('routeData : ' + routeData); + setRouteCoordinates(newRouteCoordinates); setDistance(routeDistance); @@ -470,7 +470,7 @@ const MapView = () => { setIsModalVisible(false)} - dogs={myDogInfo} + dogs={myDogInfo.data} onSelectMultipleDogs={handleSelectDog} type="multi-select" /> From abc21746d00aa0aa19ef1b1e49a7e350431d97fa Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 12:16:24 +0900 Subject: [PATCH 35/45] =?UTF-8?q?fix:=20=EA=B0=95=EC=95=84=EC=A7=80=20?= =?UTF-8?q?=EC=84=B1=EB=B3=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Common/ListModal/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Common/ListModal/index.tsx b/src/components/Common/ListModal/index.tsx index 5a7764a..c5b644a 100644 --- a/src/components/Common/ListModal/index.tsx +++ b/src/components/Common/ListModal/index.tsx @@ -49,6 +49,8 @@ export const DogListModal = ({ const slideAnim = useRef(new Animated.Value(type === 'walk' ? WALK_MODAL_HEIGHT : 500)).current; const [selectedDogs, setSelectedDogs] = useState([]); + console.log(dogs); + useEffect(() => { if (isVisible) { Animated.spring(slideAnim, { @@ -125,7 +127,7 @@ export const DogListModal = ({ {calculateAge(dog.dogBirthDate)}살 - {dog.dogGender} + {dog.dogGender === 'MALE' ? '남' : '여'} 산책 횟수 {dog.walkCount}회 From 795788555a78f53568622f7cf23d5f0dfb2cafb1 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 12:17:18 +0900 Subject: [PATCH 36/45] =?UTF-8?q?fix:=20=EC=82=B0=EC=B1=85=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EC=8B=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/walk/completeWalk.ts | 94 ++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/src/apis/walk/completeWalk.ts b/src/apis/walk/completeWalk.ts index dfec6d8..f1744aa 100644 --- a/src/apis/walk/completeWalk.ts +++ b/src/apis/walk/completeWalk.ts @@ -38,21 +38,91 @@ export interface ResponseCompleteWalk { }; } -export const completeWalk = async (userInfo: RequestCompleteWalk): Promise> => { +const processImageForFormData = (imageUri: string): any => { + console.log('[API] 이미지 처리 시작, URI 유형:', typeof imageUri); + + if (!imageUri) { + console.error('[API] 이미지 URI가 없음'); + throw new Error('이미지 URI가 제공되지 않았습니다.'); + } + try { - const response = api - .post('walk/complete', { - json: userInfo, - }) - .json>(); - return response; + if (imageUri.startsWith('file://')) { + console.log('[API] 로컬 파일 URI 감지:', imageUri.substring(0, 40) + '...'); + return { + uri: imageUri, + name: `walk-map-${Date.now()}.png`, + type: 'image/png', + }; + } + + if (imageUri.startsWith('data:image/')) { + console.log('[API] Data URI 감지 (base64)'); + return { + uri: imageUri, + name: `walk-map-${Date.now()}.png`, + type: imageUri.split(';')[0].replace('data:', ''), + }; + } + + console.log('[API] 기타 형식의 URI, 기본 처리 적용'); + return { + uri: imageUri, + name: `walk-map-${Date.now()}.png`, + type: 'image/png', + }; } catch (error) { - if (error instanceof HTTPError) { - const errorData = await error.response.json(); - console.error(errorData); - } else { - console.error(error); + console.error('[API] 이미지 처리 실패:', error); + throw new Error('이미지 변환에 실패했습니다: ' + (error instanceof Error ? error.message : '알 수 없는 오류')); + } +}; + +export const completeWalk = async (userInfo: RequestCompleteWalk): Promise> => { + try { + console.log('[API] completeWalk 요청 시작:', userInfo); + + const formData = new FormData(); + + const mapImageFile = processImageForFormData(userInfo.walkImgFile); + console.log('[API] 처리된 이미지 파일:', mapImageFile.name); + formData.append('walkImgFile', mapImageFile); + + const requestData = JSON.stringify({ + totalDistanceMeter: userInfo.request.totalDistanceMeter, + totalWalkTimeSecond: userInfo.request.totalWalkTimeSecond, + }); + + console.log('[API] JSON 요청 데이터:', requestData); + formData.append('request', requestData); + + console.log('[API] FormData 구성 완료'); + + console.log('[API] API 요청 시작'); + + try { + const response = await api + .post('walk/complete', { + body: formData, + headers: { + Accept: 'application/json', + }, + timeout: 30000, // 30초 타임아웃 (파일 업로드는 시간이 더 걸릴 수 있음) + }) + .json>(); + + console.log('[API] completeWalk 응답 성공:', response); + return response; + } catch (error) { + if (error instanceof HTTPError) { + const errorData = await error.response.json(); + console.error('[API] HTTP 오류:', errorData); + } else { + console.error('[API] 알 수 없는 오류:', error); + } + throw error; } + } catch (error) { + console.error('[API] completeWalk 오류:', error); throw error; } }; From 94132a6f54238d2811f9a6891363628a11a3250c Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 12:18:47 +0900 Subject: [PATCH 37/45] =?UTF-8?q?feat:=20=ED=98=BC=EC=9E=90=20=EC=82=B0?= =?UTF-8?q?=EC=B1=85=20=EC=95=8C=EA=B3=A0=EB=A6=AC=EC=A6=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/MapView/index.tsx | 663 ++++++++++++++++++-------- 1 file changed, 456 insertions(+), 207 deletions(-) diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index 9aaa1be..a9b416f 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -6,15 +6,14 @@ import { NaverMapCircleOverlay, NaverMapPathOverlay, } from '@mj-studio/react-native-naver-map'; -import { useEffect, useRef, useState } from 'react'; -import { Platform } from 'react-native'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { Alert, Linking, Platform } from 'react-native'; import Geolocation from '@react-native-community/geolocation'; import { request, PERMISSIONS, requestLocationAccuracy, requestMultiple } from 'react-native-permissions'; import { formatDuration, formatDistance } from '~screens/Home/WalkScreen'; import * as S from './styles'; import { useMyDogInfo } from '~apis/dog/useMyDogInfo'; import { DogListModal } from '~components/Common/ListModal'; -// import ky from 'ky'; import axios from 'axios'; import ViewShot, { captureRef } from 'react-native-view-shot'; @@ -23,11 +22,14 @@ import WalkSummaryModal from '../WalkSummary'; import { startWalk } from '~apis/walk/startWalk'; import { completeWalk } from '~apis/walk/completeWalk'; import { useWebSocket } from '~hooks/useWebSocket'; +import React from 'react'; +import { getAvatar } from '~utils/getAvatar'; +import { useUser } from '~apis/member/useUser'; const WALKING_INTERVAL = 5000; -// const NORMAL_INTERVAL = 10000; const MIN_ACCURACY = 30; -const MIN_MARKER_DISTANCE = 1; +const MIN_MARKER_DISTANCE = 20; +const ROUTE_API_URL = 'https://ruehan-home.com:8004/ors/v2/directions/foot-walking/geojson'; const calculateDirectDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => { const R = 6371e3; @@ -44,26 +46,31 @@ const calculateDirectDistance = (lat1: number, lon1: number, lat2: number, lon2: const MapView = () => { const myDogInfo = useMyDogInfo(); - const { sendMessage, responseData } = useWebSocket(); + const { sendMessage, responseData, connectionStatus } = useWebSocket(); + const avatars = getAvatar(); + const user = useUser(); + const AvatarComponent = avatars[user.memberProfileImg]; - console.log('받은 데이터 : ' + responseData); + console.log('[Walk] 유저 정보:', user); + + useEffect(() => { + if (responseData) { + console.log('[Walk] WebSocket 응답 데이터 수신:', responseData); + } + }, [responseData]); + + console.log('[Walk] WebSocket 연결 상태:', connectionStatus); const mapRef = useRef(null); const [isWalking, setIsWalking] = useState(false); const [walkTime, setWalkTime] = useState(0); const [distance, setDistance] = useState(0); - const [camera, _setCamera] = useState({ latitude: 37.50497126, longitude: 127.04905021, zoom: 18, }); - const [_previousLocation, setPreviousLocation] = useState<{ - latitude: number; - longitude: number; - } | null>(null); - const [currentLocation, setCurrentLocation] = useState<{ latitude: number; longitude: number; @@ -71,7 +78,7 @@ const MapView = () => { latitude: 37.50497126, longitude: 127.04905021, }); - + const previousLocationRef = useRef<{ latitude: number; longitude: number } | null>(null); const [locationMarkers, setLocationMarkers] = useState< { latitude: number; @@ -79,14 +86,15 @@ const MapView = () => { index: number; }[] >([]); + const lastUpdateTimeRef = useRef(Date.now()); - const [lastUpdateTime, setLastUpdateTime] = useState(Date.now()); const [isLocationCentered, setIsLocationCentered] = useState(true); const [isModalVisible, setIsModalVisible] = useState(false); const [routeCoordinates, setRouteCoordinates] = useState([]); const viewShotRef = useRef(null); const [screenshotUri, setScreenshotUri] = useState(''); const [isWalkSummaryVisible, setIsWalkSummaryVisible] = useState(false); + const [locationPermissionGranted, setLocationPermissionGranted] = useState(false); useEffect(() => { let interval: NodeJS.Timeout; @@ -97,70 +105,188 @@ const MapView = () => { } return () => clearInterval(interval); }, [isWalking]); - const filterPosition = (position: { coords: { accuracy: number; latitude: number; longitude: number } }): boolean => { - return position.coords.accuracy <= MIN_ACCURACY; - }; - - const shouldAddMarker = (newPosition: { latitude: number; longitude: number }): boolean => { - if (locationMarkers.length === 0) { - return true; + const isAccurate = position.coords.accuracy <= MIN_ACCURACY; + if (!isAccurate) { + console.log('[Walk] 위치 정확도 낮음, 무시됨:', position.coords.accuracy); } - - const lastMarker = locationMarkers[locationMarkers.length - 1]; - const calDistance = calculateDirectDistance( - lastMarker.latitude, - lastMarker.longitude, - newPosition.latitude, - newPosition.longitude, - ); - - return calDistance >= MIN_MARKER_DISTANCE; + return isAccurate; }; + const shouldAddMarker = useCallback( + (newPosition: { latitude: number; longitude: number }): boolean => { + if (locationMarkers.length === 0) { + console.log('[Walk] 첫 마커 추가'); + return true; + } - useEffect(() => { - const requestLocationPermission = async () => { + const lastMarker = locationMarkers[locationMarkers.length - 1]; + const calDistance = calculateDirectDistance( + lastMarker.latitude, + lastMarker.longitude, + newPosition.latitude, + newPosition.longitude, + ); + + const shouldAdd = calDistance >= MIN_MARKER_DISTANCE; + console.log('[Walk] 마커 추가 여부:', shouldAdd, '거리:', calDistance.toFixed(2) + 'm'); + return shouldAdd; + }, + [locationMarkers], + ); + const requestLocationPermission = useCallback(async () => { + try { if (Platform.OS === 'ios') { const status = await request(PERMISSIONS.IOS.LOCATION_ALWAYS); + console.log('[Walk] iOS 위치 권한 상태:', status); + if (status === 'granted') { try { await requestLocationAccuracy({ purposeKey: 'common-purpose' }); + setLocationPermissionGranted(true); } catch (e) { - console.error('iOS 위치 정확도 요청 실패:', e); + console.error('[Walk] iOS 위치 정확도 요청 실패:', e); + Alert.alert('위치 정확도 설정 필요', '정확한 위치 정보가 필요합니다. 설정에서 권한을 변경해주세요.', [ + { text: '취소', style: 'cancel' }, + { + text: '설정으로 이동', + onPress: () => { + Linking.openURL('app-settings:'); + }, + }, + ]); } + } else if (status === 'denied' || status === 'blocked') { + Alert.alert( + '위치 권한이 필요합니다', + '산책 기능을 사용하려면 위치 권한이 필요합니다. 설정에서 위치 권한을 허용해주세요.', + [ + { text: '취소', style: 'cancel' }, + { + text: '설정으로 이동', + onPress: () => { + Linking.openURL('app-settings:'); + }, + }, + ], + ); + } else { + Alert.alert('위치 권한 필요', '산책 기능을 사용하려면 항상 또는 앱 사용 중 위치 권한이 필요합니다.'); } } else { - try { - await requestMultiple([ - PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, - PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION, - ]); - } catch (e) { - console.error('Android 위치 권한 요청 실패:', e); + const statuses = await requestMultiple([ + PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, + PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION, + ]); + console.log('[Walk] Android 위치 권한 상태:', statuses); + + if (statuses[PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION] === 'granted') { + setLocationPermissionGranted(true); + } else if ( + statuses[PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION] === 'denied' || + statuses[PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION] === 'blocked' + ) { + Alert.alert( + '위치 권한이 필요합니다', + '산책 기능을 사용하려면 위치 권한이 필요합니다. 설정에서 위치 권한을 허용해주세요.', + [ + { text: '취소', style: 'cancel' }, + { + text: '설정으로 이동', + onPress: () => { + Linking.openSettings(); + }, + }, + ], + ); + } else { + Alert.alert('위치 권한 필요', '산책 기능을 사용하려면 위치 권한이 필요합니다.'); } } + } catch (e) { + console.error('[Walk] 위치 권한 요청 실패:', e); + Alert.alert('오류', '위치 권한을 요청하는 중 오류가 발생했습니다.'); + } + }, []); + + useEffect(() => { + const initializeLocation = async () => { + await requestLocationPermission(); + + Geolocation.getCurrentPosition( + position => { + const { latitude, longitude } = position.coords; + console.log('[Walk] 초기 위치 가져오기 성공:', { latitude, longitude }); + + setCurrentLocation({ latitude, longitude }); + + mapRef.current?.animateCameraTo({ + latitude, + longitude, + zoom: 18, + duration: 500, + easing: 'Fly', + }); + }, + error => { + console.error('[Walk] 초기 위치 가져오기 실패:', error); + Alert.alert('위치 오류', '현재 위치를 가져올 수 없습니다. 위치 서비스가 활성화되어 있는지 확인해주세요.'); + }, + { + enableHighAccuracy: true, + timeout: 5000, + }, + ); }; - requestLocationPermission(); + initializeLocation(); + }, [requestLocationPermission]); + + useEffect(() => { + if (!locationPermissionGranted) { + console.log('[Walk] 위치 권한 없음, 추적 중단'); + return; + } + + console.log('[Walk] 위치 추적 시작 (권한 있음)'); const watchId = Geolocation.watchPosition( position => { const currentTime = Date.now(); - const timeSinceLastUpdate = currentTime - lastUpdateTime; + const timeSinceLastUpdate = currentTime - lastUpdateTimeRef.current; + + console.log('[Walk] 위치 데이터 수신:', { + accuracy: position.coords.accuracy, + lastUpdate: timeSinceLastUpdate, + isWalking: isWalking, + coords: { + latitude: position.coords.latitude, + longitude: position.coords.longitude, + }, + }); if (timeSinceLastUpdate < WALKING_INTERVAL) { return; } if (!filterPosition(position)) { + console.log('[Walk] 정확도 불충분, 무시:', position.coords.accuracy); return; } const { latitude, longitude } = position.coords; const newPosition = { latitude, longitude }; + previousLocationRef.current = currentLocation; + setCurrentLocation(newPosition); + lastUpdateTimeRef.current = currentTime; + + console.log('[Walk] 위치 업데이트 완료:', { latitude, longitude }); + if (isWalking) { + console.log('[Walk] 산책 중 위치 업데이트 처리'); + if (shouldAddMarker(newPosition)) { + console.log('[Walk] 새 마커 추가:', newPosition); setLocationMarkers(prev => [ ...prev, { @@ -171,81 +297,38 @@ const MapView = () => { ]); } - mapRef.current?.animateCameraTo({ - latitude, - longitude, - zoom: 18, - duration: 500, - easing: 'Fly', - }); + if (isLocationCentered) { + console.log('[Walk] 카메라 위치 이동'); + mapRef.current?.animateCameraTo({ + latitude, + longitude, + zoom: 18, + duration: 500, + easing: 'Fly', + }); + } } - - setPreviousLocation(currentLocation); - setCurrentLocation(newPosition); - setLastUpdateTime(currentTime); }, error => { - console.error('위치 추적 오류:', error); + console.error('[Walk] 위치 추적 오류:', error); + Alert.alert('위치 추적 오류', '위치를 추적하는 중 오류가 발생했습니다.'); }, { enableHighAccuracy: true, distanceFilter: 0, interval: 1000, - timeout: 5000, + timeout: 10000, // 타임아웃을 10초로 늘림 }, ); + return () => { + console.log('[Walk] 위치 추적 중지'); Geolocation.clearWatch(watchId); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentLocation, isWalking, lastUpdateTime]); + }, [locationPermissionGranted, isWalking, isLocationCentered, shouldAddMarker]); - useEffect(() => { - const getCurrentLocation = async () => { - try { - if (Platform.OS === 'ios') { - const status = await request(PERMISSIONS.IOS.LOCATION_ALWAYS); - if (status === 'granted') { - await requestLocationAccuracy({ purposeKey: 'common-purpose' }); - } - } else { - await requestMultiple([ - PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION, - PERMISSIONS.ANDROID.ACCESS_BACKGROUND_LOCATION, - ]); - } - - Geolocation.getCurrentPosition( - position => { - const { latitude, longitude } = position.coords; - - setCurrentLocation({ latitude, longitude }); - - mapRef.current?.animateCameraTo({ - latitude, - longitude, - zoom: 18, - duration: 500, - easing: 'Fly', - }); - }, - error => { - console.error('초기 위치 가져오기 실패:', error); - }, - { - enableHighAccuracy: true, - timeout: 5000, - }, - ); - } catch (error) { - console.error('위치 권한 요청 실패:', error); - } - }; - - getCurrentLocation(); - }, []); - - const handleLocationButtonPress = () => { + const handleLocationButtonPress = useCallback(() => { + console.log('[Walk] 내 위치로 이동'); mapRef.current?.animateCameraTo({ latitude: currentLocation.latitude, longitude: currentLocation.longitude, @@ -253,66 +336,174 @@ const MapView = () => { duration: 500, easing: 'Fly', }); - }; + setIsLocationCentered(true); + }, [currentLocation]); + + const handleStartWalkPress = useCallback(() => { + console.log('[Walk] 산책 시작 버튼 클릭'); + + if (!locationPermissionGranted) { + console.log('[Walk] 위치 권한 없음, 권한 요청 시도'); + Alert.alert('위치 권한 필요', '산책 기능을 사용하려면 위치 권한이 필요합니다. 권한을 허용하시겠습니까?', [ + { text: '취소', style: 'cancel' }, + { + text: '권한 요청', + onPress: async () => { + await requestLocationPermission(); + if (locationPermissionGranted) { + setIsModalVisible(true); + } + }, + }, + ]); + return; + } - const handleStartWalkPress = () => { setIsModalVisible(true); - }; + }, [locationPermissionGranted, requestLocationPermission]); - const handleStopWalkPress = async () => { + const handleStopWalkPress = useCallback(async () => { + console.log('[Walk] 산책 종료 버튼 클릭'); setIsWalking(false); try { + console.log('[Walk] 스크린샷 촬영 시작'); const uri = await captureRef(viewShotRef, { format: 'png', quality: 0.8, }); + const savedUri = await CameraRoll.save(uri, { type: 'photo' }); + console.log('[Walk] 스크린샷 저장 성공:', savedUri); - console.log(walkTime); - console.log(distance); - console.log(uri); - console.log(savedUri); + console.log('[Walk] 산책 데이터 준비:', { + 시간: walkTime, + 거리: distance, + 이미지URI: uri.substring(0, 50) + '...', + }); const response = await completeWalk({ request: { totalDistanceMeter: distance, totalWalkTimeSecond: walkTime, }, - walkImgFile: savedUri, + walkImgFile: uri, }); - console.log('산책 완료 API 호출 성공:', response); + + console.log('[Walk] 산책 완료 API 성공:', response); setScreenshotUri(savedUri); setIsWalkSummaryVisible(true); } catch (error) { - console.error('산책 종료 이미지 저장 실패:', error); + console.error('[Walk] 산책 종료 처리 실패:', error); + if (error instanceof Error) { + console.error('[Walk] 오류 메시지:', error.message); + console.error('[Walk] 오류 스택:', error.stack); + } + Alert.alert('오류', '산책 종료 처리 중 오류가 발생했습니다.'); } - }; + }, [walkTime, distance]); - // WalkSummaryModal이 닫힐 때 호출되는 함수 - const handleWalkSummaryClose = () => { + const handleWalkSummaryClose = useCallback(() => { + console.log('[Walk] 산책 요약 닫기'); setWalkTime(0); setDistance(0); setLocationMarkers([]); + setRouteCoordinates([]); setIsWalkSummaryVisible(false); - }; + }, []); - const handleSelectDog = async (dog: any) => { - console.log(dog); - setIsModalVisible(false); - setIsWalking(true); + const handleSelectDog = useCallback( + async (dogs: any) => { + console.log('[Walk] 선택된 강아지:', dogs); + setIsModalVisible(false); - try { - const dogIds = dog.map((d: any) => d.dogId); - // const response = await startWalk({ dogIds }); - console.log(response); - } catch (error) { - console.error('산책 시작 실패:', error); - } - }; + try { + const dogIds = dogs.map((d: any) => d.dogId); + console.log('[Walk] 산책 시작 강아지 IDs:', dogIds); + + const response = await startWalk({ dogIds }); + console.log('[Walk] 산책 시작 API 성공:', response); + + if ( + !currentLocation || + (currentLocation.latitude === 37.50497126 && currentLocation.longitude === 127.04905021) + ) { + console.log('[Walk] 현재 위치 업데이트 필요, 위치 가져오기 시도'); + + Geolocation.getCurrentPosition( + position => { + const { latitude, longitude } = position.coords; + console.log('[Walk] 산책 시작 전 위치 업데이트 성공:', { latitude, longitude }); + + setCurrentLocation({ latitude, longitude }); + + mapRef.current?.animateCameraTo({ + latitude, + longitude, + zoom: 18, + duration: 500, + easing: 'Fly', + }); + + setLocationMarkers([ + { + latitude, + longitude, + index: 0, + }, + ]); + + setIsWalking(true); + setIsLocationCentered(true); + setDistance(0); + lastProcessedMarkerIndexRef.current = -1; + }, + error => { + console.error('[Walk] 산책 시작 위치 가져오기 실패:', error); + Alert.alert( + '위치 오류', + '현재 위치를 가져올 수 없습니다. 위치 서비스가 활성화되어 있는지 확인 후 다시 시도해주세요.', + ); + }, + { + enableHighAccuracy: true, + timeout: 10000, + }, + ); + } else { + console.log('[Walk] 현재 위치로 산책 시작:', currentLocation); + + mapRef.current?.animateCameraTo({ + latitude: currentLocation.latitude, + longitude: currentLocation.longitude, + zoom: 18, + duration: 500, + easing: 'Fly', + }); + + setLocationMarkers([ + { + latitude: currentLocation.latitude, + longitude: currentLocation.longitude, + index: 0, + }, + ]); + + setIsWalking(true); + setIsLocationCentered(true); + setDistance(0); + lastProcessedMarkerIndexRef.current = -1; + } + } catch (error) { + console.error('[Walk] 산책 시작 실패:', error); + Alert.alert('오류', '산책을 시작하는 중 오류가 발생했습니다.'); + } + }, + [currentLocation], + ); - const renderWalkButton = () => { + const renderWalkButton = useCallback(() => { if (!isWalking) { return ; } @@ -326,89 +517,140 @@ const MapView = () => { ); - }; + }, [isWalking, walkTime, distance, handleStartWalkPress, handleStopWalkPress]); - const handleCameraChange = (event: any) => { - const { latitude, longitude } = event; + const handleCameraChange = useCallback( + (event: any) => { + const { latitude, longitude } = event; - const calDistance = calculateDirectDistance( - latitude, - longitude, - currentLocation.latitude, - currentLocation.longitude, - ); + const calDistance = calculateDirectDistance( + latitude, + longitude, + currentLocation.latitude, + currentLocation.longitude, + ); - setIsLocationCentered(calDistance < 20); - }; + const centered = calDistance < 20; + if (isLocationCentered !== centered) { + setIsLocationCentered(centered); + console.log('[Walk] 지도 중심 상태 변경:', centered); + } + }, + [currentLocation, isLocationCentered], + ); - // eslint-disable-next-line @typescript-eslint/no-shadow - const fetchRouteData = async (locationMarkers: { longitude: number; latitude: number }[]) => { - if (locationMarkers.length < 2) { - return; - } + const fetchRouteData = useCallback( + async (markers: { longitude: number; latitude: number }[]) => { + if (markers.length < 2) { + console.log('[Walk] 경로 계산 건너뜀: 마커가 부족함'); + return; + } - const lastTwoCoordinates = [ - [locationMarkers[locationMarkers.length - 2].longitude, locationMarkers[locationMarkers.length - 2].latitude], - [locationMarkers[locationMarkers.length - 1].longitude, locationMarkers[locationMarkers.length - 1].latitude], - ]; + const lastTwoMarkers = markers.slice(-2); + const lastTwoCoordinates = lastTwoMarkers.map(marker => [marker.longitude, marker.latitude]); - console.log('lastTwoCoordinates : ' + lastTwoCoordinates); - console.log('lastTwoCoordinates : ' + typeof lastTwoCoordinates); + console.log('[Walk] 경로 계산 요청:', lastTwoCoordinates); - try { - // const response = await ky - // .post('https://ruehan-home.com:8003/ors/v2/directions/foot-walking/geojson', { - // json: { - // coordinates: lastTwoCoordinates, - // }, - // }) - // .json(); - // const routeData = response as { - // features: [ - // { - // geometry: { - // coordinates: number[][]; - // }; - // properties: { - // segments: [ - // { - // distance: number; - // }, - // ]; - // }; - // }, - // ]; - // }; - - const response = await axios.post('https://ruehan-home.com:8004/ors/v2/directions/foot-walking/geojson', { - coordinates: lastTwoCoordinates, - }); - const routeData = response.data; - const newRouteCoordinates = routeData.features[0].geometry.coordinates; - const routeDistance = routeData.features[0].properties.segments[0].distance; + try { + const response = await axios.post( + ROUTE_API_URL, + { + coordinates: lastTwoCoordinates, + }, + { + timeout: 10000, + }, + ); - console.log('routeData : ' + routeData); + const routeData = response.data; - setRouteCoordinates(newRouteCoordinates); - setDistance(routeDistance); + if ( + !routeData?.features?.[0]?.geometry?.coordinates || + !routeData?.features?.[0]?.properties?.segments?.[0]?.distance + ) { + console.error('[Walk] 경로 데이터 형식 오류:', routeData); + return; + } - const lastCoordinate = newRouteCoordinates[newRouteCoordinates.length - 1]; - const message = JSON.stringify({ - latitude: lastCoordinate[1], - longitude: lastCoordinate[0], - }); - // sendMessage('/pub/api/v1/walk-alone', message); - console.log('메시지 보내기 완료'); - } catch (error) { - console.error('경로 데이터 가져오기 실패:', error); - } - }; + const newRouteCoordinates = routeData.features[0].geometry.coordinates; + const segmentDistance = routeData.features[0].properties.segments[0].distance; + + console.log('[Walk] 경로 계산 성공:', { + 좌표수: newRouteCoordinates.length, + 구간거리: segmentDistance, + 현재총거리: distance, + }); + + setRouteCoordinates(prev => { + if ( + prev.length > 0 && + prev[prev.length - 1][0] === newRouteCoordinates[0][0] && + prev[prev.length - 1][1] === newRouteCoordinates[0][1] + ) { + return [...prev, ...newRouteCoordinates.slice(1)]; + } + return [...prev, ...newRouteCoordinates]; + }); + + if (lastProcessedMarkerIndexRef.current === 2) { + console.log('[Walk] 첫 구간 거리 설정:', segmentDistance); + setDistance(segmentDistance); + } else if (segmentDistance < 1000) { + console.log('[Walk] 거리 추가:', distance, '+', segmentDistance, '=', distance + segmentDistance); + setDistance(prevDistance => prevDistance + segmentDistance); + } else { + console.warn('[Walk] 비정상적으로 큰 구간 거리 무시:', segmentDistance); + } + + if (connectionStatus === 'connected' && newRouteCoordinates.length > 0) { + const currentTotalDistance = + lastProcessedMarkerIndexRef.current === 2 + ? segmentDistance + : segmentDistance < 1000 + ? distance + segmentDistance + : distance; - const drawRoutePolygon = () => { + const lastCoordinate = newRouteCoordinates[newRouteCoordinates.length - 1]; + + const message = JSON.stringify({ + latitude: lastCoordinate[1], + longitude: lastCoordinate[0], + }); + + console.log('[Walk] WebSocket publish 시도 - /pub/api/v1/walk-alone'); + const sent = sendMessage('/pub/api/v1/walk-alone', message); + console.log('[Walk] WebSocket 메시지 전송 결과:', sent); + + if (sent) { + console.log('[Walk] 메시지 내용:', JSON.parse(message)); + } else { + console.error('[Walk] WebSocket 메시지 전송 실패'); + } + } else { + console.log('[Walk] WebSocket 메시지 전송 건너뜀:', { + 연결상태: connectionStatus, + 좌표수: newRouteCoordinates ? newRouteCoordinates.length : 0, + }); + } + } catch (error) { + console.error('[Walk] 경로 데이터 가져오기 실패:', error); + if (axios.isAxiosError(error)) { + console.error('[Walk] 오류 세부 정보:', { + message: error.message, + status: error.response?.status, + data: error.response?.data, + }); + } + } + }, + [connectionStatus, sendMessage, distance, walkTime], + ); + + const drawRoutePolygon = useCallback(() => { if (isWalking && routeCoordinates.length >= 2) { + console.log('[Walk] 경로 폴리곤 그리기:', routeCoordinates.length, '개 좌표'); return ( ({ latitude, longitude }))} @@ -416,13 +658,19 @@ const MapView = () => { ); } return null; - }; + }, [isWalking, routeCoordinates]); + + const lastProcessedMarkerIndexRef = useRef(-1); useEffect(() => { if (isWalking && locationMarkers.length >= 2) { - fetchRouteData(locationMarkers); + if (locationMarkers.length > lastProcessedMarkerIndexRef.current) { + console.log(`[Walk] 새 마커 처리: ${lastProcessedMarkerIndexRef.current + 1} -> ${locationMarkers.length}`); + fetchRouteData(locationMarkers); + lastProcessedMarkerIndexRef.current = locationMarkers.length; + } } - }, [locationMarkers, isWalking]); + }, [locationMarkers, isWalking, fetchRouteData]); return ( <> @@ -442,8 +690,9 @@ const MapView = () => { anchor={{ x: 0.5, y: 1 }} width={40} height={40} - image={require('../../../assets/avatars/Avatar1.png')} - /> + > + {AvatarComponent && } + {locationMarkers.map((marker, index) => ( Date: Fri, 28 Feb 2025 12:19:47 +0900 Subject: [PATCH 38/45] =?UTF-8?q?fix:=20=EC=9B=B9=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useWebSocket.ts | 97 +++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts index aa81c3b..595aebf 100644 --- a/src/hooks/useWebSocket.ts +++ b/src/hooks/useWebSocket.ts @@ -1,55 +1,126 @@ import { useEffect, useRef, useState } from 'react'; import SockJS from 'sockjs-client'; import { Client, IMessage } from '@stomp/stompjs'; - -const token = - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsInByb3ZpZGVyIjoiS0FLQU8iLCJleHAiOjE3Mzk3NjQwMDIsImVtYWlsIjoibWtoNjc5M0BuYXZlci5jb20ifQ.EF03NpevMSZ2DcM5Q-trEEmRa0KEb5HpJ1HlD-Vj8xy3N2JoFvdQFoWDJRM3IGVwx58L9T2oV7GBTr6wJOevnA'; +import { getAccessToken } from '~utils/controlAccessToken'; +import { useUser } from '~apis/member/useUser'; const SERVER_URL = 'https://ddang.site/ws'; export const useWebSocket = () => { const stompClientRef = useRef(null); const [responseData, setResponseData] = useState(null); + const [accessToken, setAccessToken] = useState(null); + const [connectionStatus, setConnectionStatus] = useState<'disconnected' | 'connecting' | 'connected'>('disconnected'); + const reconnectAttemptRef = useRef(0); + + const { email } = useUser(); + + console.log('[WebSocket] User email:', email); + // Token 가져오기 useEffect(() => { + const getToken = async () => { + const Token = await getAccessToken(); + console.log('[WebSocket] Token retrieved:', Token ? 'Success' : 'Failed'); + setAccessToken(Token); + }; + getToken(); + }, []); + + // WebSocket 연결 설정 + useEffect(() => { + if (!accessToken || accessToken === 'none' || !email) { + console.log('[WebSocket] Connection skipped - missing token or email'); + return; + } + + console.log('[WebSocket] Setting up connection with token and email:', email); + setConnectionStatus('connecting'); + const stompClient = new Client({ webSocketFactory: () => new SockJS(SERVER_URL), reconnectDelay: 5000, - debug: msg => console.log(msg), + debug: process.env.NODE_ENV === 'development' ? msg => console.log('[WebSocket Debug]', msg) : undefined, connectHeaders: { - Authorization: `Bearer ${token}`, + Authorization: `Bearer ${accessToken}`, }, }); stompClient.onConnect = frame => { - console.log('STOMP 연결 성공:', frame); + console.log('[WebSocket] Connection established:', frame?.command); + setConnectionStatus('connected'); + reconnectAttemptRef.current = 0; + + // 이메일로 구독 - walk 응답 처리 + console.log(`[WebSocket] Subscribing to /sub/walk/${email}`); + stompClient.subscribe(`/sub/walk/${email}`, message => { + try { + const response = JSON.parse(message.body); + console.log('[WebSocket] Message received from /sub/walk:', response); - stompClient.subscribe('/sub/walk/mkh6793@naver.com', message => { - const response = JSON.parse(message.body); - console.log('받은 메시지:', response); - setResponseData(response); + // 응답 데이터 저장 및 처리 + setResponseData(prevData => { + console.log('[WebSocket] Updating response data:', { + previous: prevData, + new: response, + }); + return response; + }); + } catch (error) { + console.error('[WebSocket] Error parsing message:', error); + } }); }; + // 연결 오류 처리 + stompClient.onStompError = frame => { + console.error('[WebSocket] Connection error:', frame.headers?.message); + setConnectionStatus('disconnected'); + reconnectAttemptRef.current += 1; + }; + + stompClient.onWebSocketClose = () => { + console.log('[WebSocket] Connection closed'); + setConnectionStatus('disconnected'); + }; + stompClient.activate(); stompClientRef.current = stompClient; return () => { - stompClient.deactivate(); + console.log('[WebSocket] Cleaning up connection'); + if (stompClient.connected) { + stompClient.deactivate(); + } }; - }, []); + }, [accessToken, email]); // email과 accessToken 변경 시 재연결 const sendMessage = (destination: string, body: string) => { if (stompClientRef.current && stompClientRef.current.connected) { + console.log(`[WebSocket] Sending message to ${destination}:`, body); stompClientRef.current.publish({ destination, body }); + return true; + } else { + console.warn('[WebSocket] Cannot send message - not connected'); + return false; } }; const subscribe = (destination: string, callback: (message: IMessage) => void) => { if (stompClientRef.current && stompClientRef.current.connected) { + console.log(`[WebSocket] Subscribing to ${destination}`); return stompClientRef.current.subscribe(destination, callback); + } else { + console.warn('[WebSocket] Cannot subscribe - not connected'); + return undefined; } }; - return { client: stompClientRef.current, sendMessage, subscribe, responseData }; + return { + client: stompClientRef.current, + sendMessage, + subscribe, + responseData, + connectionStatus, + }; }; From 0601d13478941691b71f85946c6a2f293f61771b Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 12:20:23 +0900 Subject: [PATCH 39/45] chore: install axios --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e2edb6..c8be550 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,8 @@ "@emotion/native": "^11.11.0", "@emotion/react": "^11.14.0", "@mj-studio/react-native-naver-map": "^2.2.0", - "@react-native-camera-roll/camera-roll": "^7.9.0", - "@react-native-community/geolocation": "^3.4.0", "@react-native-async-storage/async-storage": "^2.1.1", + "@react-native-camera-roll/camera-roll": "^7.9.0", "@react-native-community/geolocation": "^3.4.0", "@react-native-community/netinfo": "^11.4.1", "@react-navigation/bottom-tabs": "^7.2.0", @@ -22,6 +21,7 @@ "@react-navigation/stack": "^7.1.1", "@stomp/stompjs": "^7.0.0", "@tanstack/react-query": "^5.63.0", + "axios": "^1.7.9", "d3": "^7.9.0", "fast-text-encoding": "^1.0.6", "jotai": "^2.11.1", diff --git a/package.json b/package.json index 2be05be..c7e9039 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,8 @@ "@emotion/native": "^11.11.0", "@emotion/react": "^11.14.0", "@mj-studio/react-native-naver-map": "^2.2.0", - "@react-native-camera-roll/camera-roll": "^7.9.0", - "@react-native-community/geolocation": "^3.4.0", "@react-native-async-storage/async-storage": "^2.1.1", + "@react-native-camera-roll/camera-roll": "^7.9.0", "@react-native-community/geolocation": "^3.4.0", "@react-native-community/netinfo": "^11.4.1", "@react-navigation/bottom-tabs": "^7.2.0", @@ -25,6 +24,7 @@ "@react-navigation/stack": "^7.1.1", "@stomp/stompjs": "^7.0.0", "@tanstack/react-query": "^5.63.0", + "axios": "^1.7.9", "d3": "^7.9.0", "fast-text-encoding": "^1.0.6", "jotai": "^2.11.1", From 0c8c830727018b0e49043de10f0e6e28bb057e2b Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 12:20:38 +0900 Subject: [PATCH 40/45] fix: Info.plist --- ios/DDang/Info.plist | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/ios/DDang/Info.plist b/ios/DDang/Info.plist index 34a9498..167e5f1 100644 --- a/ios/DDang/Info.plist +++ b/ios/DDang/Info.plist @@ -30,6 +30,8 @@ NSAllowsArbitraryLoads + NSAllowsLocalNetworking + NSExceptionDomains ddang.site @@ -40,17 +42,17 @@ - NSAllowsLocalNetworking - - NSLocationAlwaysAndWhenInUseUsageDescription - 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. - NSLocationTemporaryUsageDescriptionDictionary - 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. + NSAppleMusicUsageDescription + 사진 등록시 사진 선택을 위해 라이브러리 접근 권한이 필요합니다. + NSCameraUsageDescription + 카메라 촬영시 카메라 접근 권한이 필요합니다. NSLocationAlwaysAndWhenInUseUsageDescription 내 위치 정보 표시와 산책 기능 제공을 위해 권한 허용이 필요합니다. 설정에서 언제든 변경이 가능합니다. NSLocationAlwaysUsageDescription 내 위치 정보 표시와 산책 기능 제공을 위해 권한 허용이 필요합니다. 설정에서 언제든 변경이 가능합니다. + NSLocationTemporaryUsageDescriptionDictionary + 네이버 맵에서 내 위치를 표시하기 위해 권한을 요청합니다. NSLocationWhenInUseUsageDescription 내 위치 정보 표시와 산책 기능 제공을 위해 권한 허용이 필요합니다. 설정에서 언제든 변경이 가능합니다. NSMotionUsageDescription @@ -59,27 +61,8 @@ 사진 등록시 사진 선택을 위해 라이브러리 접근 권한이 필요합니다. NSPhotoLibraryUsageDescription 사진 등록시 사진 선택을 위해 라이브러리 접근 권한이 필요합니다. - NSAppleMusicUsageDescription - 사진 등록시 사진 선택을 위해 라이브러리 접근 권한이 필요합니다. - NSCameraUsageDescription - 카메라 촬영시 카메라 접근 권한이 필요합니다. NSUserNotificationUsageDescription 앱에서 알림을 보내기 위해 권한이 필요합니다. - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - arm64 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - UIAppFonts AntDesign.ttf From fe1e0193bb31cdc550eea9cbb5685a5781af361b Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 13:43:53 +0900 Subject: [PATCH 41/45] =?UTF-8?q?feat:=20=EC=A3=BC=EC=86=8C=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/Header/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/Walk/Header/index.tsx b/src/components/Walk/Header/index.tsx index 062f8cb..f090047 100644 --- a/src/components/Walk/Header/index.tsx +++ b/src/components/Walk/Header/index.tsx @@ -1,10 +1,16 @@ import { useNavigation } from '@react-navigation/native'; import * as S from './styles'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import React from 'react'; +import { getAvatar } from '~utils/getAvatar'; +import { useUser } from '~apis/member/useUser'; const WalkHeader = () => { const navigation = useNavigation(); const insets = useSafeAreaInsets(); + const avatars = getAvatar(); + const user = useUser(); + const AvatarComponent = avatars[user.memberProfileImg]; return ( @@ -17,10 +23,8 @@ const WalkHeader = () => { navigation.goBack()}> - 강남구 논현동 - - - + {user.address} + {AvatarComponent && } ); From 9eb5adb78e451587c3aea94b9464ec6990077c7d Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 13:44:32 +0900 Subject: [PATCH 42/45] =?UTF-8?q?fix:=20=EA=B0=95=EB=B2=88=EB=94=B0=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/screens/Home/WalkScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Home/WalkScreen.tsx b/src/screens/Home/WalkScreen.tsx index e12d9a4..e7d322e 100644 --- a/src/screens/Home/WalkScreen.tsx +++ b/src/screens/Home/WalkScreen.tsx @@ -37,7 +37,7 @@ export const WalkScreen = () => { - + {/* */} ); From bf2e331f225a4087a82920b9f63b10217cdea8b4 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 13:45:59 +0900 Subject: [PATCH 43/45] =?UTF-8?q?chore:=20Podfile=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b476ef7..129c7c0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1269,6 +1269,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-cameraroll (7.9.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-config (1.5.5): - react-native-config/App (= 1.5.5) - react-native-config/App (1.5.5): @@ -1430,6 +1451,27 @@ PODS: - Yoga - react-native-splash-screen (3.3.0): - React-Core + - react-native-view-shot (4.0.3): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-webview (13.13.2): - DoubleConversion - glog @@ -2051,6 +2093,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - react-native-config (from `../node_modules/react-native-config`) - react-native-date-picker (from `../node_modules/react-native-date-picker`) - react-native-encrypted-storage (from `../node_modules/react-native-encrypted-storage`) @@ -2059,6 +2102,7 @@ DEPENDENCIES: - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - "react-native-slider (from `../node_modules/@react-native-community/slider`)" - react-native-splash-screen (from `../node_modules/react-native-splash-screen`) + - react-native-view-shot (from `../node_modules/react-native-view-shot`) - react-native-webview (from `../node_modules/react-native-webview`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) @@ -2181,6 +2225,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-cameraroll: + :path: "../node_modules/@react-native-camera-roll/camera-roll" react-native-config: :path: "../node_modules/react-native-config" react-native-date-picker: @@ -2197,6 +2243,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-community/slider" react-native-splash-screen: :path: "../node_modules/react-native-splash-screen" + react-native-view-shot: + :path: "../node_modules/react-native-view-shot" react-native-webview: :path: "../node_modules/react-native-webview" React-nativeconfig: @@ -2314,6 +2362,7 @@ SPEC CHECKSUMS: React-logger: ae95f0effa7e1791bd6f7283caddca323d4fbc1e React-Mapbuffer: 7eb5d69e1154e7743487ef0c8d7261e5b59afb32 React-microtasksnativemodule: 01dd998649ff5f8814846b7eee84c4d57f5d3671 + react-native-cameraroll: 31e39d303319ba20657808f33104afa9d52d83aa react-native-config: 644074ab88db883fcfaa584f03520ec29589d7df react-native-date-picker: 26cdb1a94ec72dbc9210c3379e57ff6ba8bc73f2 react-native-encrypted-storage: 569d114e329b1c2c2d9f8c84bcdbe4478dda2258 @@ -2322,6 +2371,7 @@ SPEC CHECKSUMS: react-native-safe-area-context: 5e53e2b0fc3a2994ad0c89a2486e545b6566d8c4 react-native-slider: d1a9121980fc81678c6d30b82f312c778fba563c react-native-splash-screen: 95994222cc95c236bd3cdc59fe45ed5f27969594 + react-native-view-shot: 60117a0ac87a504a39ee9f079e632011916e7724 react-native-webview: dd55f7d3b8c0dcdf8835a44ffc5bd03303dffb30 React-nativeconfig: f7ab6c152e780b99a8c17448f2d99cf5f69a2311 React-NativeModulesApple: 9aeb901b9bfcc9235e912445fb3cf4780a99baf4 From 69a245966ead48765e89643cf59b6c060252cd83 Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 13:56:00 +0900 Subject: [PATCH 44/45] =?UTF-8?q?fix=20:=20lint=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 -- src/components/Common/ListModal/index.tsx | 1 + src/components/Walk/MapView/index.tsx | 9 ++------- src/components/Walk/WalkSummary/styles.ts | 1 - src/screens/Home/WalkScreen.tsx | 1 - 5 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a804154..a0f2312 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,8 +5,6 @@ import { AppProviders } from '~providers/AppProviders'; import { lightTheme } from '~styles/theme'; import StoryBookUI from '../.storybook'; import { RootNavigator } from '~navigation/RootNavigator'; -import { useWebSocket } from '~hooks/useWebSocket'; -import { WebSocketProvider } from '~providers/WebSocketProvider'; import 'react-native-url-polyfill/auto'; import 'fast-text-encoding'; diff --git a/src/components/Common/ListModal/index.tsx b/src/components/Common/ListModal/index.tsx index c5b644a..c1f9ace 100644 --- a/src/components/Common/ListModal/index.tsx +++ b/src/components/Common/ListModal/index.tsx @@ -67,6 +67,7 @@ export const DogListModal = ({ friction: 10, }).start(); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isVisible, type]); const toggleDogSelection = (dog: Dog) => { diff --git a/src/components/Walk/MapView/index.tsx b/src/components/Walk/MapView/index.tsx index a9b416f..61e1b13 100644 --- a/src/components/Walk/MapView/index.tsx +++ b/src/components/Walk/MapView/index.tsx @@ -325,6 +325,7 @@ const MapView = () => { console.log('[Walk] 위치 추적 중지'); Geolocation.clearWatch(watchId); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [locationPermissionGranted, isWalking, isLocationCentered, shouldAddMarker]); const handleLocationButtonPress = useCallback(() => { @@ -603,13 +604,6 @@ const MapView = () => { } if (connectionStatus === 'connected' && newRouteCoordinates.length > 0) { - const currentTotalDistance = - lastProcessedMarkerIndexRef.current === 2 - ? segmentDistance - : segmentDistance < 1000 - ? distance + segmentDistance - : distance; - const lastCoordinate = newRouteCoordinates[newRouteCoordinates.length - 1]; const message = JSON.stringify({ @@ -643,6 +637,7 @@ const MapView = () => { } } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [connectionStatus, sendMessage, distance, walkTime], ); diff --git a/src/components/Walk/WalkSummary/styles.ts b/src/components/Walk/WalkSummary/styles.ts index bdec8e6..85e0032 100644 --- a/src/components/Walk/WalkSummary/styles.ts +++ b/src/components/Walk/WalkSummary/styles.ts @@ -1,5 +1,4 @@ import styled from '@emotion/native'; -import { Button } from 'react-native'; import { BgBox } from '~components/Common/BgBox'; import { TextBold, TextExtraBold, TextMedium } from '~components/Common/Text'; diff --git a/src/screens/Home/WalkScreen.tsx b/src/screens/Home/WalkScreen.tsx index e7d322e..7277725 100644 --- a/src/screens/Home/WalkScreen.tsx +++ b/src/screens/Home/WalkScreen.tsx @@ -2,7 +2,6 @@ import { useNavigation } from '@react-navigation/native'; import { useLayoutEffect } from 'react'; import WalkHeader from '~components/Walk/Header'; import MapView from '~components/Walk/MapView'; -import WalkMessage from '~components/Walk/WalkMessage'; import { SafeAreaView, View } from 'react-native'; export const formatDuration = (seconds: number): string => { From d164b771b58967f333655beecf5365701ceb96da Mon Sep 17 00:00:00 2001 From: GYU HAN Date: Fri, 28 Feb 2025 14:05:53 +0900 Subject: [PATCH 45/45] =?UTF-8?q?fix:=20lint=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Walk/WalkSummary/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Walk/WalkSummary/index.tsx b/src/components/Walk/WalkSummary/index.tsx index 6e22a53..5be88a8 100644 --- a/src/components/Walk/WalkSummary/index.tsx +++ b/src/components/Walk/WalkSummary/index.tsx @@ -10,7 +10,7 @@ interface WalkSummaryModalProps { onClose: () => void; } -const WalkSummaryModal = ({ visible, walkTime, distance, screenshotUri, onClose }: WalkSummaryModalProps) => { +const WalkSummaryModal = ({ visible, walkTime, distance, screenshotUri }: WalkSummaryModalProps) => { if (!visible) { return null; }