Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
59b997b
[#36] feat : useLocation 리팩토링
jaewonLeeKOR Jan 22, 2026
34cc446
[#36] feat : 지도 SafeAreaView 해제
jaewonLeeKOR Jan 22, 2026
60c7945
Merge branch 'develop' into feat/refactorMapView/#36
jaewonLeeKOR Feb 3, 2026
bbd972b
[#36] feat : 클라이언트 위치 조회 방식 watch 방식으로 변경
jaewonLeeKOR Feb 3, 2026
227e4be
[#36] feat : 바텀 네비게이션 바 아이콘 높이 제한 해제
jaewonLeeKOR Feb 3, 2026
a7c342f
[#36] feat : 클러스터링 베이스 지도 구현
jaewonLeeKOR Feb 3, 2026
63a4341
[#36] feat : 지도탭 통합 중간 커밋
jaewonLeeKOR Mar 15, 2026
a4a73c3
Merge branch 'develop' into feat/integrateMapViewTap/#36
Imdahee Mar 18, 2026
592a1da
Merge branch 'develop' into feat/integrateMapViewTap/#36
Imdahee Mar 27, 2026
3cfdb42
[#36]feat : 지도 탭 최적화
Imdahee Apr 3, 2026
0f3cf0a
[#36]feat: curation에서 sightDetail로 렌더링
Imdahee Apr 7, 2026
c4de169
[#36]fix: sightDetail 스크롤 수정 / pagerView 수정
Imdahee Apr 7, 2026
6b0141e
[#36] fix: id 타입을 number로 통일
Imdahee Apr 13, 2026
a9b14c5
[#36] refactor :
Imdahee Apr 16, 2026
5074b30
[#36] fix: storyAdd CenterPin 위치 반환
Imdahee Apr 16, 2026
5ce8ab8
[#36]feat: story 기능 통합 및 지도 인터랙션 개선
Imdahee Apr 18, 2026
e48db90
[#36]style: 파일 구조 정리
Imdahee Apr 19, 2026
861b3e8
[#36]feat: 상태에 따라 네비게이션바 숨김 로직 추가
Imdahee Apr 19, 2026
981eaff
[#36] feat: storySpotLocation UI 변경
Imdahee Apr 20, 2026
58f81f2
[#36]fix : curation closeButton 활성화
Imdahee Apr 22, 2026
52039c9
[#36] fix : storyAdd UI 수정
Imdahee Apr 22, 2026
0805e67
[#36]style: 불필요한 로그 삭제
Imdahee Apr 22, 2026
65d4fbc
[#36]feat: 검색 화면 구현
Imdahee Apr 29, 2026
daf3cac
Merge remote-tracking branch 'origin/develop' into feat/integrateMapV…
Imdahee Apr 30, 2026
83949c3
[#36]fix : mapType storyAddBottomSheet 제거 및 바텀시트 snapPoint 추가
Imdahee May 5, 2026
95ad7f7
[#36]fix: 라이브러리의 최소 버전을 12.0 설정
Imdahee May 5, 2026
66fd0b1
[#36]fix: 라이브러리의 최소 버전을 15.1 설정
Imdahee May 5, 2026
d7f0a1e
[#36]fix : 누락된 파일 추가
Imdahee May 6, 2026
e2634f4
[#36]fix : myPage safeArea 추가
Imdahee May 12, 2026
f2da54b
[#36]fix: 지도 드래그 시 디바운스 추가 및 store에 값 반환으로 수정
Imdahee May 13, 2026
98978b2
[#36]fix: sight 마커 선택 에러 수정
Imdahee May 13, 2026
dc32296
[#36] fix : 앱 재실행 후 로그인 상태가 복구되도록 loadUser 호출 추가
manbron236 May 14, 2026
d486f1c
[#36] fix : 위치정보 에러 수정
Imdahee May 21, 2026
504ff31
[#36]fix: 초기 마커 렌더링 에러 수정 및 주석추가
Imdahee May 21, 2026
47f55ec
[#36]fix : 탭 전환 시 마커 안뜨는 에러 수정
Imdahee May 22, 2026
ac4436e
[#36] fix: loadMarkerFromStore storedBounds 우선 사용으로 초기 마커 로드 개선
Imdahee May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ export default {
userInterfaceStyle: "automatic",
newArchEnabled: true,
updates: {
url: "https://u.expo.dev/51f8ef22-b7f8-40ed-92d1-201c776e3b87"
url: "https://u.expo.dev/51f8ef22-b7f8-40ed-92d1-201c776e3b87",
},
runtimeVersion: {
policy: "appVersion"
policy: "appVersion",
},
extra: {
APP_ENV_API_BASE_URL: process.env.APP_ENV_API_BASE_URL,
APP_ENV_GEOFENCE_RADIUS: process.env.APP_ENV_GEOFENCE_RADIUS || '100',
APP_ENV_GEOFENCE_RADIUS: process.env.APP_ENV_GEOFENCE_RADIUS || "100",
eas: {
projectId: "51f8ef22-b7f8-40ed-92d1-201c776e3b87"
}
projectId: "51f8ef22-b7f8-40ed-92d1-201c776e3b87",
},
},
ios: {
bundleIdentifier: "com.earseo.earseo",
Expand All @@ -32,7 +32,7 @@ export default {
UIBackgroundModes: ["audio", "location"],
},
config: {
usesNonExemptEncryption: false
usesNonExemptEncryption: false,
},
icon: "./src/assets/earseo-ios.icon",
displayName: "이어서",
Expand Down Expand Up @@ -84,4 +84,4 @@ export default {
reactCompiler: true,
},
},
};
};
174 changes: 174 additions & 0 deletions app/(tabs)/(index,story,route)/[mapType].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React, { useEffect, useRef } from "react";

import { InteractionManager } from "react-native";
import { useSharedValue } from "react-native-reanimated";

import { BottomSheetMethods } from "@gorhom/bottom-sheet/src/types";
import { useGlobalSearchParams } from "expo-router";

import CurationBottomSheet from "@/components/bottomSheet/CurationBottomSheet";
import CustomBottomSheet from "@/components/bottomSheet/CustomBottomSheet";
import RouteBottomSheet from "@/components/bottomSheet/RouteBottomSheet";
import StoryBottomSheet from "@/components/bottomSheet/StoryBottomSheet";
import BaseMap from "@/components/map/BaseMap";
import MapHeader from "@/components/map/header/MapHeader";
import MapSearchBar from "@/components/map/header/MapSearchBar";

import { useBaseMap } from "@/hooks/map/useBaseMap";
import { useSightMap } from "@/hooks/sight/useSightMap";
import { useStorySpotMap } from "@/hooks/story/useStorySpotMap";

import { useBottomSheetStore } from "@/store/common/useBottomSheetStore";
import { useBaseMapStore } from "@/store/map/useBaseMapStore";
import { useMapHeaderStore } from "@/store/map/useMapHeaderStore";
import { useRouteStore } from "@/store/route/useRouteStore";
import { useSightStore } from "@/store/sight/useSightStore";
import { useStoryAddStore } from "@/store/story/useStoryAddStore";
import { useStoryStore } from "@/store/story/useStoryStore";

function MapTabScreen() {
const { mapType } = useGlobalSearchParams();
const bottomSheetRef = useRef<BottomSheetMethods>(null);
const bottomSheetPosition = useSharedValue(0);
const { setBottomSheetContent, setBottomSheetLayout, setIsBottomSheetDragg } =
useBottomSheetStore();
const { setMapHeaderContent } = useMapHeaderStore();
const {
setMapMovable,
setEnableCluster,
setCenterPinVisibility,
setRegionChangeCompleteMethod,
} = useBaseMapStore();
const { loadMarkerFromStore } = useBaseMap();
//sight
const { fetchSightInBoundingBox } = useSightMap();
const { setSights } = useSightStore();
//story
const { fetchStorySpotInBoundingBox } = useStorySpotMap();
const { selectedStorySpot, setSpotListInMap } = useStoryStore();
const { setNewSpotName } = useStoryAddStore();
const storyAddStep = useStoryAddStore((state) => state.storyAddStep);
//route
const { setPathVisibility } = useRouteStore();

useEffect(() => {
/**TODO
* 화면 별 요소 추가
* - 초기 마커 컴포넌트 O
* - regionChange 시 마커 로드 함수 O
* - 검색바 컴포넌트 O
* - 바텀시트 내부 컴포넌트 O
* - 바텀시트 위치 조정 X
*
* TODO issue: param이 변경될때마다 세번씩 호출됨 O
*/
if (!mapType) return;
setRegionChangeCompleteMethod(undefined);
if (mapType === "route") {
//TODO 경로 지도용 요소 추가
setBottomSheetLayout({
snapPoints: ["15%", "45%", "75%", "100%"],
index: 1,
});
setSights([]);
setEnableCluster(false);
setPathVisibility(true);
setSpotListInMap([]);
setBottomSheetContent(<RouteBottomSheet />);
} else if (mapType === "sight") {
//TODO 관광지 지도용 요소 추가
setBottomSheetLayout({
snapPoints: ["15%", "45%", "75%", "100%"],
index: 1,
});
setMapHeaderContent(
<MapSearchBar
placeHolder={"관광지 검색.."}
onPressMapSearchBar={() => {
//TODO 관광지 검색 모달
console.log("sight map search bar pressed");
}}
/>
);
setBottomSheetContent(<CurationBottomSheet />);
}

if (mapType === "story") {
setBottomSheetContent(<StoryBottomSheet />);
if (storyAddStep === "none") {
setBottomSheetLayout({
snapPoints: ["15%", "45%", "75%", "100%"],
index: 1,
});
setCenterPinVisibility(false);
setMapHeaderContent(
<MapSearchBar
placeHolder={"이야기 검색.."}
onPressMapSearchBar={() => {
console.log("story map search bar pressed");
}}
/>
);
}
if (storyAddStep === "location") {
setCenterPinVisibility(true);
setIsBottomSheetDragg(false);
setBottomSheetLayout({
snapPoints: ["30%"],
index: 0,
});
}

if (storyAddStep === "storyAdd") {
setCenterPinVisibility(false);
setIsBottomSheetDragg(true);
setBottomSheetLayout({
snapPoints: ["100%"],
index: 0,
});
}
}

const task = InteractionManager.runAfterInteractions(() => {
if (mapType === "sight") {
setRegionChangeCompleteMethod(fetchSightInBoundingBox);
loadMarkerFromStore();
} else if (mapType === "story" && storyAddStep === "none") {
setRegionChangeCompleteMethod(fetchStorySpotInBoundingBox);
loadMarkerFromStore();
}
});

return () => {
// store 기반 공유 컴포넌트 데이터 초기화
task.cancel();
setBottomSheetLayout({
snapPoints: ["15%", "45%", "75%", "100%"],
index: 1,
});
setMapHeaderContent(undefined);
setMapMovable(true);
setEnableCluster(true);
setPathVisibility(false);
setSights([]);
setBottomSheetContent(undefined);
selectedStorySpot([]);
setIsBottomSheetDragg(true);
setCenterPinVisibility(false);
setNewSpotName(undefined);
};
}, [mapType, storyAddStep]);

return (
<>
<BaseMap bottomSheetPosition={bottomSheetPosition} />
<MapHeader />
<CustomBottomSheet
bottomSheetRef={bottomSheetRef}
animatedPosition={bottomSheetPosition}
/>
</>
);
}

export default React.memo(MapTabScreen);
105 changes: 71 additions & 34 deletions app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
import { Ionicons } from "@expo/vector-icons";
import { Tabs } from "expo-router";
import { Tabs, usePathname } from "expo-router";

import CourseIcon from "@/components/icons/tabs/CourseIcon";
import MyRouteIcon from "@/components/icons/tabs/MyRouteIcon";

import { theme } from "@/styles/theme";

import { useNavigationBarStore } from "@/store/common/useNavigationBarStore";

export default function TabLayout() {
const pathname = usePathname();
const currentMapType = pathname.startsWith("/sight")
? "sight"
: pathname.startsWith("/route")
? "route"
: pathname.startsWith("/story")
? "story"
: null;
const { isNavigationBarHidden } = useNavigationBarStore();
return (
<Tabs
backBehavior="history"
screenOptions={{
headerShown: false,
tabBarStyle: {
height: 15,
paddingTop: 15,
paddingHorizontal: 10,
borderTopWidth: 1,
position: "static",
overflow: "hidden",
borderTopColor: theme.colors.grey.neutral200,
backgroundColor: theme.colors.white,
},
tabBarStyle: isNavigationBarHidden
? { display: "none" }
: {
paddingTop: 15,
paddingHorizontal: 10,
borderTopWidth: 1,
position: "static",
overflow: "hidden",
borderTopColor: theme.colors.grey.neutral200,
backgroundColor: theme.colors.white,
},
}}
>
<Tabs.Screen
Expand All @@ -38,42 +50,67 @@ export default function TabLayout() {
),
}}
/>

{/*@deprecated*/}

<Tabs.Screen
name="myRoute"
name="(route)/[mapType]"
options={{
title: "route",
href: {
pathname: "/(tabs)/(index)/[mapType]",
params: { mapType: "route" },
},
tabBarLabel: () => null,
tabBarIcon: ({ focused, color, size }) => (
<MyRouteIcon
size={24}
color={focused ? theme.colors.main.primary : theme.colors.black}
></MyRouteIcon>
),
tabBarIcon: ({ color }) => {
color =
currentMapType === "route"
? theme.colors.main.primary
: theme.colors.black;
return <MyRouteIcon size={24} color={color} />;
},
}}
/>
<Tabs.Screen
name="index"
name="(index)/[mapType]"
options={{
title: "sight",
href: {
pathname: "/(tabs)/(index)/[mapType]",
params: { mapType: "sight" },
},
tabBarLabel: () => null,
tabBarIcon: ({ focused, color, size }) => (
<CourseIcon
size={26}
color={focused ? theme.colors.main.primary : theme.colors.black}
></CourseIcon>
),
tabBarIcon: ({ color }) => {
color =
currentMapType === "sight"
? theme.colors.main.primary
: theme.colors.black;
return <CourseIcon size={26} color={color} />;
},
}}
/>
<Tabs.Screen
name="story"
name="(story)/[mapType]"
options={{
href: "/story",
title: "story",
href: {
pathname: "/(tabs)/(index)/[mapType]",
params: { mapType: "story" },
},
tabBarLabel: () => null,
tabBarIcon: ({ focused, color, size }) => (
<Ionicons
name="chatbox-ellipses-outline"
size={28}
color={focused ? theme.colors.main.primary : theme.colors.black}
/>
),
tabBarIcon: ({ color }) => {
color =
currentMapType === "story"
? theme.colors.main.primary
: theme.colors.black;
return (
<Ionicons
name="chatbox-ellipses-outline"
size={28}
color={color}
/>
);
},
}}
/>
<Tabs.Screen
Expand Down
14 changes: 9 additions & 5 deletions app/(tabs)/docent.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { StyleSheet } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";

import EmptyTour from "@/components/docent/emptyTour/EmptyTour";
import OnTour from "@/components/docent/onTour/OnTour";

import { useRouteStore } from "@/store/useRouteStore";
import { useRouteStore } from "@/store/route/useRouteStore";

export default function Docent() {
const { routeItems } = useRouteStore();

if (routeItems === undefined) {
return <EmptyTour />;
}
return <OnTour />;
return (
<SafeAreaView style={[StyleSheet.absoluteFill]}>
{routeItems === undefined ? <EmptyTour /> : <OnTour />}
</SafeAreaView>
);
}
Loading
Loading