diff --git a/app/ios/app.xcodeproj/project.pbxproj b/app/ios/app.xcodeproj/project.pbxproj index 00341aa..5c86017 100644 --- a/app/ios/app.xcodeproj/project.pbxproj +++ b/app/ios/app.xcodeproj/project.pbxproj @@ -530,7 +530,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.3; + MARKETING_VERSION = 1.1.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -561,7 +561,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.3; + MARKETING_VERSION = 1.1.1; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -659,10 +659,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -745,10 +742,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/app/src/components/HomeNavigator.tsx b/app/src/components/HomeNavigator.tsx index 153dd8a..5ad835b 100644 --- a/app/src/components/HomeNavigator.tsx +++ b/app/src/components/HomeNavigator.tsx @@ -1,9 +1,7 @@ -import {NavigationContainer} from '@react-navigation/native'; import {createNativeStackNavigator} from '@react-navigation/native-stack'; import Home from 'screens/home/Home'; import PassPort from 'screens/passport/PassPort'; -import NewTrash from 'screens/NewTrash'; -import NewTrashDetail from 'screens/NewTrashDetail'; +import ReportNewBin from 'screens/reportNewBin/ReportNewBin'; export default function HomeNavigator() { const Stack = createNativeStackNavigator(); @@ -11,8 +9,7 @@ export default function HomeNavigator() { - - + ); } diff --git a/app/src/screens/NewTrash.tsx b/app/src/screens/NewTrash.tsx deleted file mode 100644 index ce8860e..0000000 --- a/app/src/screens/NewTrash.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import {Palette} from 'constants/palette'; -import {Image, Text, View} from 'react-native'; -import styled from 'styled-components/native'; -import NewTrashLocation from 'assets/images/NewTrashLocation'; -import DefaultText from 'components/DefaultText'; -import {Typo} from 'constants/typo'; -import {NavigationProp, useNavigation} from '@react-navigation/native'; - -export default function NewTrash() { - const navigation2 = useNavigation>(); - return ( - - -
- -
- - - - 서울 성북구 삼선교로 16길 16-3 1층 101.102호 - - - -
-
- ); -} - -const Background = styled.View` - position: absolute; - flex: 1; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - z-index: 1; - bottom: 0; -`; - -const Wrapper = styled.View` - width: 100%; - height: 156px; - border-radius: 20px 20px 0px 0px; - background: ${Palette.White}; - gap: 6px; - margin-top: auto; -`; - -const Header = styled.View` - border-radius: 20px 20px 0px 0px; - padding: 8px 0px 18px; - align-items: center; - background: ${Palette.White}; -`; - -const Indicator = styled.View` - border-radius: 2px; - width: 45px; - height: 4px; - background: ${Palette.Gray2}; -`; - -const Body = styled.View` - padding: 0px 24px 0px 24px; - flex: 1; - background: ${Palette.White}; -`; - -const TextWrapper = styled.View` - flex: 1; - flex-direction: row; - flex-wrap: wrap; - align-items: center; -`; - -const Button = styled.TouchableOpacity` - width: 100%; - padding: 16px 30px; - border-radius: 10px; - background: ${Palette.Primary}; - margin-top: auto; - margin-bottom: 30px; -`; - -export const ButtonText = styled(DefaultText)` - font-size: ${Typo.Button1.fontSize}; - font-weight: ${Typo.Button1.fontWeight}; - color: ${Palette.White}; - text-align: center; -`; diff --git a/app/src/screens/home/Home.style.ts b/app/src/screens/home/Home.style.ts index c9b26ce..e05d1ce 100644 --- a/app/src/screens/home/Home.style.ts +++ b/app/src/screens/home/Home.style.ts @@ -149,7 +149,7 @@ export const Button = styled.TouchableOpacity` align-items: center; background: ${Palette.Primary}; border-radius: 10px; - margin-top: auto; + margin-top: 24px; `; export const ButtonText = styled(DefaultText)` diff --git a/app/src/screens/home/Home.tsx b/app/src/screens/home/Home.tsx index 23b1498..d389c38 100644 --- a/app/src/screens/home/Home.tsx +++ b/app/src/screens/home/Home.tsx @@ -71,9 +71,9 @@ export default function Home() { - {/* - Find any new bin? Let us know! - */} + navigation2.navigate('ReportNewBin')}> + Find any new bin? Let us know! + diff --git a/app/src/screens/reportNewBin/ReportNewBin.style.ts b/app/src/screens/reportNewBin/ReportNewBin.style.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/src/screens/reportNewBin/ReportNewBin.tsx b/app/src/screens/reportNewBin/ReportNewBin.tsx new file mode 100644 index 0000000..8450477 --- /dev/null +++ b/app/src/screens/reportNewBin/ReportNewBin.tsx @@ -0,0 +1,167 @@ +import React, {useEffect, useRef, useState} from 'react'; +import {Platform, Alert, StyleSheet, View, Image, TouchableOpacity, Dimensions} from 'react-native'; +import {request, PERMISSIONS, RESULTS} from 'react-native-permissions'; +import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated'; +import WebView from 'react-native-webview'; +import {mapStore} from 'store/Store'; + +export default function ReportNewBin() { + const webViewRef = useRef(null); + const {startWatchingPosition, stopWatchingPosition, setCurrentPosition, setCenterPosition} = mapStore(); + const [isWebViewLoaded, setIsWebViewLoaded] = useState(false); // WebView 로드 상태 + const [bottomSheetOffset, setBottomSheetOffset] = useState(0); // BottomSheet의 높이 또는 offset 상태 + const URL = 'https://binvoyage.netlify.app/reportNewBin'; + const alertShown = useRef(false); + + const {width, height} = Dimensions.get('window'); + const refreshWrapperBottom = bottomSheetOffset > 0 ? bottomSheetOffset + 10 : 40; + + const animatedStyle = useAnimatedStyle(() => { + return { + bottom: withTiming(refreshWrapperBottom, {duration: 300}), // 애니메이션 추가 + }; + }, [refreshWrapperBottom]); + + const requestPermissionAndSendLocation = async () => { + let result; + if (Platform.OS === 'android') { + result = await request(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION); + } else if (Platform.OS === 'ios') { + result = await request(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE); + } + + if (result === RESULTS.GRANTED) { + startWatchingPosition(position => { + const message = { + type: 'location', + payload: { + latitude: position.latitude, + longitude: position.longitude, + }, + }; + console.log('Sending message to WebView:', JSON.stringify(message)); + if (isWebViewLoaded && webViewRef.current) { + webViewRef.current?.postMessage(JSON.stringify(message)); + // setTimeout(() => { + // webViewRef.current?.postMessage(JSON.stringify(message)); + // }, 500); // 0.5초 지연 + } + }); + } else { + if (!alertShown.current) { + // alertShown이라는 ref 변수를 사용해 두 번 호출 방지 + Alert.alert( + 'Location Permission Needed', + 'We need your location permission to provide information about nearby bins. Please enable location permissions in Settings.', + ); + alertShown.current = true; + } + const message = { + type: 'location', + payload: { + latitude: undefined, + longitude: undefined, + }, + }; + console.log('Sending message to WebView:', JSON.stringify(message)); + if (isWebViewLoaded && webViewRef.current) { + setTimeout(() => { + webViewRef.current?.postMessage(JSON.stringify(message)); + }, 500); // 0.5초 지연 + } + setCurrentPosition({ + latitude: 37.571648599, + longitude: 126.976372775, + }); + } + }; + + useEffect(() => { + if (isWebViewLoaded) { + requestPermissionAndSendLocation(); + } + + return () => { + stopWatchingPosition(); // 컴포넌트가 언마운트될 때만 위치 추적을 중지 + }; + }, [isWebViewLoaded]); + + const refreshLocationWatching = () => { + // 기존의 위치 감시 중지 + // if (watcherId !== null) { + // Geolocation.clearWatch(watcherId); + // } + // // 새로 위치 감시 시작 + // requestPermissionAndSendLocation(); + + if (webViewRef.current) { + const message = { + type: 'refresh', + }; + // 메시지가 잘 전송되었는지 확인하기 위한 로그 + console.log('Sending message to WebView:', JSON.stringify(message)); + + webViewRef.current.postMessage(JSON.stringify(message)); + } else { + // WebView ref가 null일 때의 로그 + console.log('WebView reference is null, message not sent.'); + } + }; + + return ( + + setIsWebViewLoaded(false)} // 로딩 시작 + onLoadEnd={() => setIsWebViewLoaded(true)} // 로딩 완료/> + /> + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + webview: { + flex: 1, + }, + View: { + flex: 1, + }, + refresh: { + position: 'absolute', + right: 16, + width: 60, + height: 60, + }, + search: { + display: 'none', + position: 'absolute', + alignSelf: 'center', + }, + visible: { + display: 'flex', + }, + loadingContainer: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.5)', // 로딩 중 배경을 반투명하게 설정 + }, +}); diff --git a/app/src/types/navigator.d.ts b/app/src/types/navigator.d.ts index 7a55e89..d7b08fa 100644 --- a/app/src/types/navigator.d.ts +++ b/app/src/types/navigator.d.ts @@ -28,6 +28,7 @@ type RootStackParamList = { type RootHomeParamList = { Home: undefined; PassPort: undefined; + ReportNewBin: undefined; }; type RootBinDetailParamList = { diff --git a/web/src/App.tsx b/web/src/App.tsx index 4addc82..9186af7 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -3,6 +3,7 @@ import Map from "./components/Map"; import { mapStore } from "./store/Store"; import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; import VerifyVisit from "./components/VerifyVisit"; +import ReportNewBin from "./components/ReportNewBin"; type CurrentLocation = { latitude: number; @@ -141,6 +142,13 @@ function App() { verifyLocation && } /> + + } + /> ); diff --git a/web/src/components/ReportNewBin.tsx b/web/src/components/ReportNewBin.tsx new file mode 100644 index 0000000..68a7e99 --- /dev/null +++ b/web/src/components/ReportNewBin.tsx @@ -0,0 +1,82 @@ +import { useRef, MutableRefObject, useState, useEffect } from "react"; +import { Palette } from "../constants/palette"; + +type ReportNewBinProps = { + latitude: number; + longitude: number; +} + +const ReportNewBin = ({latitude, longitude}: ReportNewBinProps) => { + const mapRef = useRef(null); + const [isMapLoaded, setIsMapLoaded] = useState(false); + const [currentMarker, setCurrentMarker] = useState(null); + let targetMarker: kakao.maps.Marker | null = null; + + const initMap = () => { + const container = document.getElementById('reportNewBin'); + const options = { + center: new window.kakao.maps.LatLng(latitude, longitude), + level: 3, + }; + + const map = new window.kakao.maps.Map(container as HTMLElement, options); + (mapRef as MutableRefObject).current = map; + + setIsMapLoaded(true); + + // 마커 클릭 이벤트 추가 + window.kakao.maps.event.addListener(map, 'click', function (mouseEvent: any) { + const clickPosition = mouseEvent.latLng; + + // 기존 targetMarker가 있으면 삭제 + if (targetMarker) { + console.log("removed"); + targetMarker.setMap(null); + } + + // 새로운 마커 생성 + const newMarker = new window.kakao.maps.Marker({ + position: clickPosition, + map: map, + }); + + // 새로운 마커를 상태로 저장 + targetMarker = newMarker; + }); + + + // 마커 및 기타 오버레이는 맵 로드 후 지연 추가 + setTimeout(() => { + addMarkersAndOverlays(map); + }, 500); // 0.5초 지연 후 추가 + + }; + + const addMarkersAndOverlays = (map: kakao.maps.Map) => { + const currentImageSrc = "image/Current.svg"; + // const binImageSrc = "image/targetMarker.svg"; + const imageSize = new window.kakao.maps.Size(30, 30); + const imageOption = { offset: new window.kakao.maps.Point(15, 15) }; + const currentImage = new window.kakao.maps.MarkerImage(currentImageSrc, imageSize, imageOption); + const currentPosition = new window.kakao.maps.LatLng(latitude, longitude); + + const myMarker = new window.kakao.maps.Marker({ + position: currentPosition, + image: currentImage, + zIndex: 2, + }); + + myMarker.setMap(map); + setCurrentMarker(myMarker); + }; + + useEffect(() => { + window.kakao.maps.load(() => initMap()); + }, []); + + return ( +
+ ) +} + +export default ReportNewBin; \ No newline at end of file