-
Notifications
You must be signed in to change notification settings - Fork 3
[FEAT] 메뉴 검색시 지도에 핀 표시 로직, 검색 결과 조회 기능 구현 #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a7860f3
24786d4
5dfc17a
f881845
d902864
938ec53
d339910
4b94e59
ba63d9f
0944bb2
e55c7ce
cfceb91
9d4780c
82fac6b
9f6b258
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Row | |||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.height | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.layout.size | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
|
|
@@ -137,25 +136,28 @@ fun MenuInfoImage( | |||
| ) { | ||||
| val imgUrls = menuInfoData.menuImgUrls | ||||
|
|
||||
| Row(modifier = modifier, horizontalArrangement = Arrangement.SpaceBetween) { | ||||
| Row( | ||||
| modifier = modifier, | ||||
| horizontalArrangement = Arrangement.spacedBy(4.dp) | ||||
| ) { | ||||
| for (i in 0 until 3) { | ||||
| Image( | ||||
| painter = if (i < imgUrls.size && imgUrls[i].isNotEmpty()) { | ||||
| rememberAsyncImagePainter( | ||||
| model = ImageRequest.Builder(LocalPlatformContext.current) | ||||
| .data(imgUrls[i]) | ||||
| .size(104, 80) | ||||
| .size(108, 80) | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Coil Compose는 이미지의 실제 레이아웃 사이즈를 전달해주므로 명시적 픽셀 사이즈는 불필요하고, 밀도에 따라 품질 이슈가 납니다. 제거 권장. - .size(108, 80)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||
| .build() | ||||
| ) | ||||
| } else { | ||||
| painterResource(R.drawable.img_dummy_menu) | ||||
| }, | ||||
| contentDescription = null, | ||||
| modifier = Modifier | ||||
| .size(104.dp, 80.dp) | ||||
| .weight(1f) | ||||
| .height(80.dp) | ||||
| .clip(shape = RoundedCornerShape(8.dp)) | ||||
| ) | ||||
| // if (i != 2) Spacer(modifier = Modifier.padding(end = 4.dp)) | ||||
| } | ||||
| } | ||||
| } | ||||
|
|
||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.kuit.ourmenu.ui.searchmenu.screen | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.Manifest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.content.Intent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.util.Log | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.activity.compose.BackHandler | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.foundation.interaction.MutableInteractionSource | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -32,6 +33,7 @@ import androidx.compose.ui.platform.LocalFocusManager | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.tooling.preview.Preview | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.unit.dp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.compose.ui.viewinterop.AndroidView | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.core.net.toUri | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.hilt.navigation.compose.hiltViewModel | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.kuit.ourmenu.R | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -71,18 +73,22 @@ fun SearchMenuScreen( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 지도 중심 좌표 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val currentCenter by viewModel.currentCenter.collectAsStateWithLifecycle() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 검색기록 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val searchHistory by viewModel.searchHistory.collectAsStateWithLifecycle() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 핀 위치에 해당하는 메뉴들 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val menusOnPin by viewModel.menusOnPin.collectAsStateWithLifecycle() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 선택된 라벨 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val activeMapId by viewModel.activeMapId.collectAsStateWithLifecycle() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val density = LocalDensity.current | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val singleItemHeight = 300.dp // Fixed height for each item | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LaunchedEffect(menusOnPin) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (menusOnPin != null && menusOnPin?.isNotEmpty() == true) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 메뉴핀이 선택되었을 때 바텀시트 상태 변경 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LaunchedEffect(menusOnPin, activeMapId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (activeMapId != null && menusOnPin != null && menusOnPin?.isNotEmpty() == true) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showBottomSheet = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -130,11 +136,16 @@ fun SearchMenuScreen( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BackHandler(enabled = showSearchBackground) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (searchBarFocused) focusManager.clearFocus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchActionDone = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showSearchBackground = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchText = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BackHandler(enabled = showSearchBackground || showBottomSheet) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (showSearchBackground) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (searchBarFocused) focusManager.clearFocus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchActionDone = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showSearchBackground = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchText = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (showBottomSheet) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showBottomSheet = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.clearActiveMapId() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+139
to
+148
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BottomSheetScaffold( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -160,7 +171,7 @@ fun SearchMenuScreen( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sheetPeekHeight = if(showBottomSheet) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sheetPeekHeight = if (showBottomSheet) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val itemCount = menusOnPin?.size ?: 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (singleItemHeight * itemCount) + dragHandleHeight | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else 0.dp, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+174
to
177
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion peek 높이가 화면을 초과할 수 있음 — 화면 높이 기준 상한선 필요 아이템 3개만으로도 - sheetPeekHeight = if (showBottomSheet) {
- val itemCount = menusOnPin?.size ?: 0
- (singleItemHeight * itemCount) + dragHandleHeight
- } else 0.dp,
+ sheetPeekHeight = if (showBottomSheet) {
+ val itemCount = menusOnPin?.size ?: 0
+ val desired = (singleItemHeight * itemCount) + dragHandleHeight
+ val maxPeek = with(LocalConfiguration.current) { (screenHeightDp.dp * 0.9f) }
+ desired.coerceAtMost(maxPeek)
+ } else 0.dp,추가 import 필요: import androidx.compose.ui.platform.LocalConfiguration🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -190,6 +201,7 @@ fun SearchMenuScreen( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 크롤링 기록 아이템 클릭시 동작 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.getMapMenuDetail(menuId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Log.d("SearchMenuScreen", "검색 기록 아이템 클릭: $menuId") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| focusManager.clearFocus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showSearchBackground = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showBottomSheet = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -210,37 +222,54 @@ fun SearchMenuScreen( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // onSearch 함수 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (searchBarFocused) focusManager.clearFocus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchActionDone = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 검색 시 현재 지도 중심 좌표 사용 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (searchText.isNotEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 검색 직전에 현재 지도 중심 좌표 업데이트 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.updateCurrentCenter() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val center = viewModel.getCurrentCoordinates() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (center != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val (latitude, longitude) = center | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Log.d("SearchMenuScreen", "검색 위치: $latitude, $longitude") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 검색어와 현재 좌표로 스토어 정보 요청 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.getMapSearchResult( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| query = searchText, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| long = longitude, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lat = latitude | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showBottomSheet = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showSearchBackground = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }else{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
226
to
+246
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain현재 중심 좌표 갱신/조회 사이의 경쟁 조건 가능성
다음 스크립트로 ViewModel 구현을 확인해 동기/비동기 동작을 점검해 주세요. 🏁 Script executed: #!/bin/bash
# updateCurrentCenter / getCurrentCoordinates 정의 및 사용 맥락 확인
rg -nP -C3 'fun\s+updateCurrentCenter\s*\(|fun\s+getCurrentCoordinates\s*\(' --type=ktLength of output: 115 타입 필터 문제로 기존 스크립트가 실패했습니다. 전체 🏁 Script executed: #!/bin/bash
# updateCurrentCenter 및 getCurrentCoordinates 정의 검색
rg -n -C3 'fun updateCurrentCenter' -g '*.kt'
rg -n -C3 'fun getCurrentCoordinates' -g '*.kt'Length of output: 3261 updateCurrentCenter() 비동기 호출로 인한 레이스 컨디션 위험 updateCurrentCenter()가 viewModelScope.launch 내부에서 currentCenter를 업데이트하므로 즉시 getCurrentCoordinates()를 호출하면 이전 좌표가 반환될 수 있습니다. updateCurrentCenter()를 suspend 함수로 변경해 반환값으로 center를 전달하거나, 좌표 업데이트 완료 시점에 이어서 getMapSearchResult()를 호출하도록 뷰모델 단에서 통합 처리하세요. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Log.d("SearchMenuScreen", "검색어가 비어있습니다.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.getMyMenus() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showBottomSheet = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| showSearchBackground = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| searchText = "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GoToMapButton( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modifier = Modifier | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .align(Alignment.BottomEnd) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .padding(bottom = 16.dp, end = 20.dp), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: 임시로 설정해놓은 카메라 이동, 실제로는 네이버 지도에 해당 가게 검색 결과로 이동 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewModel.moveCamera(37.5416, 127.0793) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 네이버 지도에 해당 가게 검색 결과로 이동 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (activeMapId == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Log.d("SearchMenuScreen", "활성화된 Map ID가 없습니다.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scope.launch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val searchQuery = viewModel.getWebSearchQuery(activeMapId!!) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (searchQuery.isNotBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Log.d("SearchMenuScreen", "intent query: $searchQuery") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val webIntent = Intent(Intent.ACTION_VIEW, searchQuery.toUri()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.startActivity(webIntent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+259
to
273
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 웹 이동 인텐트 가드 부족 — 스킴 검증 및 예외 처리 추가 빈 문자열 체크만으로는 부족해요. http/https 스킴 확인과 - onClick = {
+ onClick = {
// 네이버 지도에 해당 가게 검색 결과로 이동
if (activeMapId == null) {
Log.d("SearchMenuScreen", "활성화된 Map ID가 없습니다.")
} else {
scope.launch {
- val searchQuery = viewModel.getWebSearchQuery(activeMapId!!)
- if (searchQuery.isNotBlank()) {
- Log.d("SearchMenuScreen", "intent query: $searchQuery")
- val webIntent = Intent(Intent.ACTION_VIEW, searchQuery.toUri())
- context.startActivity(webIntent)
- }
+ val searchQuery = viewModel.getWebSearchQuery(activeMapId!!)
+ val uri = searchQuery.toUri()
+ if (searchQuery.isNotBlank() && (uri.scheme == "http" || uri.scheme == "https")) {
+ Log.d("SearchMenuScreen", "intent query: $searchQuery")
+ runCatching {
+ context.startActivity(Intent(Intent.ACTION_VIEW, uri))
+ }.onFailure {
+ Log.w("SearchMenuScreen", "웹 인텐트 실행 실패", it)
+ }
+ } else {
+ Log.w("SearchMenuScreen", "잘못된 URI 스킴 또는 빈 쿼리: $searchQuery")
+ }
}
}
},📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -252,7 +281,7 @@ fun SearchMenuScreen( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Composable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private fun SearchMenuScreenPreview() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SearchMenuScreen( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ){ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -268,9 +268,33 @@ class SearchMenuViewModel @Inject constructor( | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| response.onSuccess { result -> | ||||||||||||||||||||||||||
| if (result != null && result.isNotEmpty()) { | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: ${result.size}개") | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: $result") | ||||||||||||||||||||||||||
| // 검색 결과 저장 | ||||||||||||||||||||||||||
| _searchResult.value = result | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
Comment on lines
269
to
+274
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 검색 결과 전체 객체 로그 출력 지양(개인정보/노이즈) 결과 개수만 로그하세요. - Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: $result")
+ Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: ${result.size}건")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| // 전체 메뉴 목록을 다시 가져온 후 필터링 | ||||||||||||||||||||||||||
| val allMenusResponse = mapRepository.getMap() | ||||||||||||||||||||||||||
| allMenusResponse.onSuccess { allMenus -> | ||||||||||||||||||||||||||
| if (allMenus != null) { | ||||||||||||||||||||||||||
| // 전체 메뉴 중에서 검색 결과와 일치하는 것들만 필터링 | ||||||||||||||||||||||||||
| _myMenus.value = allMenus.filter { menu -> | ||||||||||||||||||||||||||
| result.any { searchResult -> searchResult.mapId == menu.mapId } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // 검색 결과의 첫 번째 항목을 활성화 상태로 설정 | ||||||||||||||||||||||||||
| _activeMapId.value = result.firstOrNull()?.mapId | ||||||||||||||||||||||||||
| showSearchResultOnMap() | ||||||||||||||||||||||||||
| // 첫 번째 검색 결과의 상세 정보를 가져와서 바텀시트에 표시 | ||||||||||||||||||||||||||
| _activeMapId.value?.let { mapId -> | ||||||||||||||||||||||||||
| getMapDetail(mapId) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // 검색 결과를 검색 기록에 반영 | ||||||||||||||||||||||||||
| if (result.firstOrNull()?.menuId != null){ | ||||||||||||||||||||||||||
| mapRepository.getMapMenuDetail(result.first().menuId) | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "검색 기록에 반영: ${result.first().menuId}") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }.onFailure { | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 실패: ${it.message}") | ||||||||||||||||||||||||||
|
|
@@ -315,37 +339,82 @@ class SearchMenuViewModel @Inject constructor( | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| fun getMapMenuDetail(menuId: Long) { | ||||||||||||||||||||||||||
| viewModelScope.launch { | ||||||||||||||||||||||||||
| val response = mapRepository.getMapMenuDetail(menuId) | ||||||||||||||||||||||||||
| response.onSuccess { menuDetail -> | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "메뉴 상세 조회 성공: $menuDetail") | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // myMenus에서 해당 menuId를 가진 메뉴의 위치 정보 찾기 | ||||||||||||||||||||||||||
| myMenus.value?.find { it.mapId == menuId }?.let { menu -> | ||||||||||||||||||||||||||
| // 해당 위치로 카메라 이동 | ||||||||||||||||||||||||||
| moveCamera(menu.mapY, menu.mapX) | ||||||||||||||||||||||||||
| // 해당 핀을 활성화 상태로 변경 | ||||||||||||||||||||||||||
| _activeMapId.value = menuId | ||||||||||||||||||||||||||
| refreshMarkers() | ||||||||||||||||||||||||||
| // 메뉴 상세 정보를 바텀시트에 표시하기 위해 설정 | ||||||||||||||||||||||||||
| getMapDetail(menuId) | ||||||||||||||||||||||||||
| // 먼저 전체 메뉴를 가져옴 | ||||||||||||||||||||||||||
| val myMenusResponse = mapRepository.getMap() | ||||||||||||||||||||||||||
| myMenusResponse.onSuccess { menus -> | ||||||||||||||||||||||||||
| if (menus != null){ | ||||||||||||||||||||||||||
| val allMenus = menus | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "menuId로 메뉴 정보 요청: $menuId") | ||||||||||||||||||||||||||
| val menuDetailResponse = mapRepository.getMapMenuDetail(menuId) | ||||||||||||||||||||||||||
| menuDetailResponse.onSuccess { menuDetail -> | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "메뉴 상세 조회 성공: $menuDetail") | ||||||||||||||||||||||||||
| // 검색 기록에서 해당 menuId를 가진 항목 찾기 | ||||||||||||||||||||||||||
| searchHistory.value?.find { it.menuId == menuId }?.let { historyItem -> | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "검색 기록에서 찾은 mapId: ${historyItem.mapId}") | ||||||||||||||||||||||||||
| // 가져온 전체 메뉴에서 필터링 | ||||||||||||||||||||||||||
| _myMenus.value = allMenus.filter { menu -> | ||||||||||||||||||||||||||
| menu.mapId == historyItem.mapId | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // 해당 mapId를 활성화 상태로 설정 | ||||||||||||||||||||||||||
| _activeMapId.value = historyItem.mapId | ||||||||||||||||||||||||||
| // 지도에 검색 결과 표시 | ||||||||||||||||||||||||||
| showSearchResultOnMap() | ||||||||||||||||||||||||||
| // 메뉴 상세 정보를 바텀시트에 표시하기 위해 설정 | ||||||||||||||||||||||||||
| getMapDetail(historyItem.mapId) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }.onFailure { | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: ${it.message}") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }.onFailure { | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: ${it.message}") | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "내 메뉴 조회 실패: ${it.message}") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+342
to
371
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chaingetMapMenuDetail: 결과 미사용 + 히스토리 미존재 fallback 부재 + 캐시 미활용
- // 먼저 전체 메뉴를 가져옴
- val myMenusResponse = mapRepository.getMap()
- myMenusResponse.onSuccess { menus ->
- if (menus != null){
- val allMenus = menus
- Log.d("SearchMenuViewModel", "menuId로 메뉴 정보 요청: $menuId")
- val menuDetailResponse = mapRepository.getMapMenuDetail(menuId)
- menuDetailResponse.onSuccess { menuDetail ->
- Log.d("SearchMenuViewModel", "메뉴 상세 조회 성공: $menuDetail")
- // 검색 기록에서 해당 menuId를 가진 항목 찾기
- searchHistory.value?.find { it.menuId == menuId }?.let { historyItem ->
- Log.d("SearchMenuViewModel", "검색 기록에서 찾은 mapId: ${historyItem.mapId}")
- // 가져온 전체 메뉴에서 필터링
- _myMenus.value = allMenus.filter { menu ->
- menu.mapId == historyItem.mapId
- }
- // 해당 mapId를 활성화 상태로 설정
- _activeMapId.value = historyItem.mapId
- // 지도에 검색 결과 표시
- showSearchResultOnMap()
- // 메뉴 상세 정보를 바텀시트에 표시하기 위해 설정
- getMapDetail(historyItem.mapId)
- }
- }.onFailure {
- Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: ${it.message}")
- }
- }
- }.onFailure {
- Log.d("SearchMenuViewModel", "내 메뉴 조회 실패: ${it.message}")
- }
+ // 캐시 우선
+ val cached = _myMenus.value
+ val allMenus = if (!cached.isNullOrEmpty()) cached else withContext(Dispatchers.IO) {
+ mapRepository.getMap().getOrElse {
+ Log.w("SearchMenuViewModel", "내 메뉴 조회 실패(캐시 사용 불가): ${it.message}")
+ null
+ }
+ } ?: emptyList()
+ if (allMenus.isEmpty()) {
+ Log.d("SearchMenuViewModel", "표시할 메뉴가 없습니다.")
+ return@launch
+ }
+
+ Log.d("SearchMenuViewModel", "menuId로 메뉴 정보 요청: $menuId")
+ val historyItem = searchHistory.value?.find { it.menuId == menuId }
+ val detailResult = withContext(Dispatchers.IO) { mapRepository.getMapMenuDetail(menuId) }
+
+ // 히스토리 우선, 없으면 상세의 mapId로 fallback
+ val targetMapId: Long? = historyItem?.mapId
+ ?: detailResult.getOrNull()?.mapId
+
+ if (targetMapId == null) {
+ Log.w("SearchMenuViewModel", "대상 mapId를 찾지 못했습니다(menuId=$menuId).")
+ return@launch
+ }
+
+ _myMenus.value = allMenus.filter { it.mapId == targetMapId }
+ _activeMapId.value = targetMapId
+ showSearchResultOnMap()
+ getMapDetail(targetMapId)
+
+ detailResult.onFailure { Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: ${it.message}") }검증 필요:
🏁 Script executed: #!/bin/bash
# MapMenuDetailResponse에 mapId 존재 여부 확인
rg -n --iglob '*.kt' "data class\s+MapMenuDetailResponse" -C3
rg -n --iglob '*.kt' "\bMapMenuDetailResponse\b" -C2Length of output: 2566 🏁 Script executed: #!/bin/bash
# Inspect MapMenuDetailResponse fields to confirm presence of mapId
sed -n '1,200p' app/src/main/java/com/kuit/ourmenu/data/model/map/response/MapMenuDetailResponse.ktLength of output: 914 SearchMenuViewModel 캐시 우선 + fallback mapId 로직 적용
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 지도에 검색 결과 핀 추가 | ||||||||||||||||||||||||||
| fun showSearchResultOnMap() { | ||||||||||||||||||||||||||
| clearMarkers() | ||||||||||||||||||||||||||
| myMenus.value?.forEach { store -> | ||||||||||||||||||||||||||
| addMarker(store, store.mapId == _activeMapId.value) | ||||||||||||||||||||||||||
| Log.d( | ||||||||||||||||||||||||||
| "SearchMenuViewModel", | ||||||||||||||||||||||||||
| "mapId: ${store.mapId} lat: (${store.mapY}, long: ${store.mapX})" | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| _myMenus.value?.let { menus -> | ||||||||||||||||||||||||||
| if (menus.isNotEmpty()) { | ||||||||||||||||||||||||||
| menus.forEach { store -> | ||||||||||||||||||||||||||
| addMarker(store, store.mapId == _activeMapId.value) | ||||||||||||||||||||||||||
| Log.d( | ||||||||||||||||||||||||||
| "SearchMenuViewModel", | ||||||||||||||||||||||||||
| "mapId: ${store.mapId} lat: (${store.mapY}, long: ${store.mapX})" | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // 첫 번째 검색 결과로 카메라 이동 TODO: 현재 위치랑 가까운 결과로 이동 | ||||||||||||||||||||||||||
| moveCamera(menus[0].mapY, menus[0].mapX) | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "검색 결과가 없습니다.") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // 첫 번째 검색 결과로 카메라 이동 TODO: 현재 위치랑 가까운 결과로 이동 | ||||||||||||||||||||||||||
| myMenus.value?.get(0)?.let { moveCamera(it.mapY, it.mapX) } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 활성화된 맵 ID를 초기화하고 마커를 다시 그림 | ||||||||||||||||||||||||||
| fun clearActiveMapId() { | ||||||||||||||||||||||||||
| _activeMapId.value = null | ||||||||||||||||||||||||||
| refreshMarkers() | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 네이버맵 이동을 위한 가게명 조회 | ||||||||||||||||||||||||||
| suspend fun getWebSearchQuery(mapId: Long): String { | ||||||||||||||||||||||||||
| val baseUrl = "https://map.naver.com/p/search/" | ||||||||||||||||||||||||||
| val response = mapRepository.getMapDetail(mapId) | ||||||||||||||||||||||||||
| return response.fold( | ||||||||||||||||||||||||||
| onSuccess = { menuList -> | ||||||||||||||||||||||||||
| if (menuList.isNullOrEmpty()) { | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: 메뉴가 없습니다.") | ||||||||||||||||||||||||||
| "" | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| baseUrl + menuList.first().storeTitle | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| onFailure = { | ||||||||||||||||||||||||||
| Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: ${it.message}") | ||||||||||||||||||||||||||
| "" | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
Comment on lines
+402
to
+418
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 웹 검색 쿼리 URL 인코딩 누락 및 불필요 재호출 가능성 공백/특수문자 포함 상호명은 인코딩 필요. 이미 _menusOnPin에 데이터가 있으면 재요청을 피하세요. - val baseUrl = "https://map.naver.com/p/search/"
- val response = mapRepository.getMapDetail(mapId)
- return response.fold(
+ val baseUrl = "https://map.naver.com/p/search/"
+ val response = mapRepository.getMapDetail(mapId)
+ return response.fold(
onSuccess = { menuList ->
if (menuList.isNullOrEmpty()) {
Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: 메뉴가 없습니다.")
""
} else {
- baseUrl + menuList.first().storeTitle
+ val encoded = java.net.URLEncoder.encode(
+ menuList.first().storeTitle,
+ java.nio.charset.StandardCharsets.UTF_8.name()
+ )
+ baseUrl + encoded
}
},
onFailure = {
Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: ${it.message}")
""
}
)
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
응답 스키마 확장(
mapId,menuId) 시 기본값 없으면 호환성 이슈구버전 서버/캐시와 통신 시 역직렬화 실패 위험. 기본값으로 방어해두는 걸 추천합니다.
data class MapSearchResponse( @SerialName("mapId") - val mapId: Long, + val mapId: Long = -1L, @SerialName("menuId") - val menuId: Long, + val menuId: Long = -1L,🏁 Script executed:
Length of output: 3803
mapId와 menuId에 기본값(-1L) 추가
역직렬화 시 필드 누락 시 MissingFieldException을 방어하려면 기본값 설정이 필요합니다 (kotlinlang.org, stackoverflow.com).
data class MapSearchResponse( @SerialName("mapId") - val mapId: Long, + val mapId: Long = -1L, @SerialName("menuId") - val menuId: Long, + val menuId: Long = -1L,📝 Committable suggestion
🤖 Prompt for AI Agents