-
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
Conversation
… into feat/mapsearch
Walkthrough지도 검색 기능 흐름을 확장: 데이터 모델에 mapId 추가, ViewModel에 activeMapId/웹검색 쿼리/클리어 로직 도입, 검색 결과를 기반으로 마커/카메라 포커싱 갱신, 화면에서 바텀시트 표시/뒤로가기 처리/웹 검색 인텐트 연동, 일부 UI 레이아웃 조정 및 프리뷰 업데이트. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Screen as SearchMenuScreen
participant VM as SearchMenuViewModel
participant Repo as DataSource
participant Map as MapView/Markers
User->>Screen: 검색어 입력 및 검색 실행
Screen->>VM: search(searchText)
VM->>Repo: mapSearch(query, center)
Repo-->>VM: MapSearchResponse(list with mapId/menuId)
VM->>Repo: getMyMenus()
Repo-->>VM: MyMenus(all)
VM->>VM: filter myMenus by mapId from results
VM->>Map: render markers(filtered)
VM->>VM: set activeMapId = firstResult.mapId
VM->>Repo: getMapDetail(activeMapId)
Repo-->>VM: MapDetail
VM-->>Screen: state(updated results, activeMapId)
Screen->>Screen: show BottomSheet when activeMapId != null
sequenceDiagram
autonumber
actor User
participant Screen as SearchMenuScreen
participant VM as SearchMenuViewModel
participant OS as Android OS Intent
User->>Screen: 히스토리 아이템 클릭
Screen->>Screen: 포커스 해제, 배경 숨김
Screen->>VM: getMapMenuDetail(menuId or mapId)
VM-->>Screen: state(activeMapId set)
Screen->>Screen: BottomSheet 표시
User->>Screen: "지도에서 보기" 클릭
Screen->>VM: getWebSearchQuery(activeMapId)
VM-->>Screen: naver search URL or ""
alt URL 존재
Screen->>OS: Intent.ACTION_VIEW(URL)
else URL 없음
Screen-->>User: 로그/무작동
end
User->>Screen: 뒤로가기
alt 검색 배경 보임
Screen->>Screen: 배경 숨김, 포커스 해제
else 바텀시트 보임
Screen->>VM: clearActiveMapId()
VM->>Map: refresh markers (clear selection)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt (2)
143-169: 이미지 스트림 누수 가능성 및 불필요한 2배 업스케일InputStream을 닫지 않고, 원본을 2배로 키워 메모리 사용량이 커집니다. 최소한 스트림 닫기/연결 해제와 다운스케일 기준 적용이 필요합니다.
- val connection = URL(url).openConnection() + val connection = (URL(url).openConnection() as java.net.HttpURLConnection) connection.connectTimeout = 5000 connection.readTimeout = 5000 connection.connect() - - val input = connection.getInputStream() - // 원본 비트맵 로드 - val originalBitmap = BitmapFactory.decodeStream(input) - - // 원하는 크기로 리사이징 (예: 원본의 2배) - originalBitmap?.let { bitmap -> - val width = bitmap.width * 2 // 원본 너비의 2배 - val height = bitmap.height * 2 // 원본 높이의 2배 - bitmap.scale(width, height).also { - // 원본 비트맵 메모리 해제 - if (it != bitmap) { - bitmap.recycle() - } - } - } + val originalBitmap = connection.inputStream.use { BitmapFactory.decodeStream(it) } + // 필요할 때에만 다운스케일 (예: 최대 96px) + originalBitmap?.let { bmp -> + val max = 96 + val scale = maxOf(bmp.width.toFloat() / max, bmp.height.toFloat() / max) + if (scale > 1f) { + val w = (bmp.width / scale).toInt() + val h = (bmp.height / scale).toInt() + bmp.scale(w, h) + } else bmp + }.also { + // HttpURLConnection 해제 + connection.disconnect() + }
- 추후 권장: Coil/Glide로 캐싱+사이즈 지정 후 Bitmap 획득. 마커 재그리기 시 네트워크 재요청을 피하려면 LruCache<url, Bitmap> 도입 권장.
204-221: 라벨 클릭 리스너 중복 등록 + 좌표 동등성 비교 취약addMarker 호출마다 setOnLabelClickListener가 재등록됩니다. 또한 실수 비교에 == 사용은 위험합니다.
- map.labelManager?.layer?.addLabel(options) - map.setOnLabelClickListener { kakaoMap, labelLayer, label -> + val label = map.labelManager?.layer?.addLabel(options) + // TODO: 초기화 시 1회만 등록(예: initializeMap에서) + map.setOnLabelClickListener { kakaoMap, labelLayer, label -> ... - _myMenus.value?.find { menu -> - menu.mapY == label.position.latitude && menu.mapX == label.position.longitude - }?.let { matchingMenu -> + _myMenus.value?.find { menu -> + kotlin.math.abs(menu.mapY - label.position.latitude) < 1e-6 && + kotlin.math.abs(menu.mapX - label.position.longitude) < 1e-6 + }?.let { matchingMenu -> ... }
- 더 좋음: 라벨 추가 시 mapId를 태그/맵에 저장하고 클릭 시 O(1)로 역참조(좌표 비교 제거).
🧹 Nitpick comments (6)
app/src/main/java/com/kuit/ourmenu/ui/common/bottomsheet/MenuInfoBottomSheetContent.kt (1)
157-159: 이미지 비율 안정화를 위해contentScale = ContentScale.Crop권장고정 높이(80.dp) + 가변 너비(가중치)라 원본 비율에 따라 레터박스/왜곡이 생길 수 있어요. Crop으로 일관되게 채우도록 제안합니다.
- contentDescription = null, + contentDescription = null, + contentScale = ContentScale.Crop, modifier = Modifier .weight(1f) .height(80.dp)app/src/main/java/com/kuit/ourmenu/ui/searchmenu/screen/SearchMenuScreen.kt (1)
90-93: 바텀시트 표시 트리거 로직 OK, 다만 중복 트리거 방지 여지
LaunchedEffect(menusOnPin, activeMapId)에서 이미 열려있을 때 또true로 세팅하는 반복이 발생할 수 있어요. 미세한 깜빡임 방지를 위해 상태 변경 최소화 권장.- if (activeMapId != null && menusOnPin != null && menusOnPin?.isNotEmpty() == true) { - showBottomSheet = true - } + if (!showBottomSheet && + activeMapId != null && + menusOnPin?.isNotEmpty() == true + ) { + showBottomSheet = true + }app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt (4)
75-102: Log 태그 명 불일치("AddMenuViewModel") → "SearchMenuViewModel"로 통일로그 필터링/검색에 불편합니다. 파일 전반에서 태그를 통일해 주세요.
- Log.d("AddMenuViewModel", "location permission : $granted") + Log.d("SearchMenuViewModel", "location permission : $granted") ... - Log.d("AddMenuViewModel", "initialize Map") + Log.d("SearchMenuViewModel", "initialize Map") ... - Log.d("AddMenuViewModel", "location success: lat=${it.latitude}, long=${it.longitude}") + Log.d("SearchMenuViewModel", "location success: lat=${it.latitude}, long=${it.longitude}") ... - Log.d("AddMenuViewModel", "location fail") + Log.d("SearchMenuViewModel", "location fail")
382-386: 카메라 포커싱은 활성 항목 우선첫 항목 고정보다 activeMapId가 있으면 그 좌표로 이동하는 게 자연스럽습니다.
- // 첫 번째 검색 결과로 카메라 이동 TODO: 현재 위치랑 가까운 결과로 이동 - moveCamera(menus[0].mapY, menus[0].mapX) + // 활성 핀으로 포커싱(없으면 첫 항목) + val focus = menus.find { it.mapId == _activeMapId.value } ?: menus.first() + moveCamera(focus.mapY, focus.mapX)
391-394: clearActiveMapId 시 바텀시트/상세 상태도 초기화UI 잔상 방지를 위해 메뉴 상세 상태를 함께 비워주세요.
fun clearActiveMapId() { _activeMapId.value = null + _menusOnPin.value = emptyList() refreshMarkers() }
227-233: 마커 재그리기 시 네트워크/디코드 반복 비용 큼(스파이크 가능)활성/비활성 아이콘을 URL→Bitmap으로 매번 로드합니다. 이미지 캐시 도입 권장.
- 제안: ViewModel 내 LruCache<String, Bitmap> 캐시를 두고, addMarker에서 캐시 히트 시 즉시 사용. 미스 시 로드 후 put.
- 활성 아이콘 토글만으로 재다운로드를 피하려면 활성/비활성 각각의 URL 키로 별도 캐시.
Also applies to: 370-387
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
app/src/main/java/com/kuit/ourmenu/data/model/map/response/MapSearchHistoryResponse.kt(1 hunks)app/src/main/java/com/kuit/ourmenu/data/model/map/response/MapSearchResponse.kt(1 hunks)app/src/main/java/com/kuit/ourmenu/ui/common/bottomsheet/MenuInfoBottomSheetContent.kt(1 hunks)app/src/main/java/com/kuit/ourmenu/ui/searchmenu/component/SearchHistory.kt(1 hunks)app/src/main/java/com/kuit/ourmenu/ui/searchmenu/screen/SearchMenuScreen.kt(8 hunks)app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/screen/SearchMenuScreen.kt (1)
app/src/main/java/com/kuit/ourmenu/ui/common/GoToMapButton.kt (1)
GoToMapButton(39-76)
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt (3)
app/src/main/java/com/kuit/ourmenu/ui/addmenu/viewmodel/AddMenuViewModel.kt (4)
showSearchResultOnMap(247-260)clearMarkers(171-175)addMarker(147-168)moveCamera(114-121)app/src/main/java/com/kuit/ourmenu/data/service/MapService.kt (2)
getMapDetail(16-57)getMapDetail(18-21)app/src/main/java/com/kuit/ourmenu/data/repository/MapRepository.kt (1)
getMapDetail(12-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
🔇 Additional comments (3)
app/src/main/java/com/kuit/ourmenu/ui/common/bottomsheet/MenuInfoBottomSheetContent.kt (1)
139-142: Row 간격 변경 깔끔합니다
Arrangement.spacedBy(4.dp)로 간격 관리하는 방향 좋아요. 내부Spacer제거와 조합이 잘 맞습니다.app/src/main/java/com/kuit/ourmenu/data/model/map/response/MapSearchHistoryResponse.kt (1)
8-10: mapId 기본값 지정 여부 검토
구버전 API 응답에mapId가 누락될 경우 역직렬화 실패(크래시)가 발생합니다. 백엔드에서 항상 내려오는지 확인하고, 보장이 없다면 기본값(val mapId: Long = -1L등)을 지정하세요.app/src/main/java/com/kuit/ourmenu/ui/searchmenu/component/SearchHistory.kt (1)
165-180: 프리뷰 데이터가 모델 변경(mapId 추가)과 정합합니다샘플 데이터 정렬/필드 추가 모두 OK.
| @SerialName("mapId") | ||
| val mapId: Long, | ||
| @SerialName("menuId") | ||
| val menuId: Long, |
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:
#!/bin/bash
set -eu
echo "Inspecting MapSearchResponse.kt for serialization annotations and imports"
sed -n '1,20p' app/src/main/java/com/kuit/ourmenu/data/model/map/response/MapSearchResponse.kt
echo
echo "Finding all references to MapSearchResponse in the codebase"
rg -n --context 2 'MapSearchResponse' -g '*.kt' app/srcLength 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
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @SerialName("mapId") | |
| val mapId: Long, | |
| @SerialName("menuId") | |
| val menuId: Long, | |
| @SerialName("mapId") | |
| val mapId: Long = -1L, | |
| @SerialName("menuId") | |
| val menuId: Long = -1L, |
🤖 Prompt for AI Agents
In
app/src/main/java/com/kuit/ourmenu/data/model/map/response/MapSearchResponse.kt
around lines 8 to 11, the properties mapId and menuId currently have no defaults
and can trigger MissingFieldException during deserialization if those fields are
absent; add default values of -1L to both properties (e.g., declare them with =
-1L) so they become optional for kotlinx.serialization and deserialization will
not throw when fields are missing.
| 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 comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Coil .size(108, 80)는 픽셀 하드코딩이라 해상도별 블러/언더페치 위험
Compose는 이미지의 실제 레이아웃 사이즈를 전달해주므로 명시적 픽셀 사이즈는 불필요하고, 밀도에 따라 품질 이슈가 납니다. 제거 권장.
- .size(108, 80)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .size(108, 80) |
🤖 Prompt for AI Agents
In
app/src/main/java/com/kuit/ourmenu/ui/common/bottomsheet/MenuInfoBottomSheetContent.kt
around line 149, remove the hardcoded Coil .size(108, 80) call (pixel values)
from the image request; instead rely on Compose to provide the image's actual
layout size (or use Coil's density-aware/ORIGINAL size options if explicit
sizing is required). Update the code to omit the .size(...) invocation so the
image scales correctly across screen densities and prevent blur/underfetch
issues.
| sheetPeekHeight = if (showBottomSheet) { | ||
| val itemCount = menusOnPin?.size ?: 0 | ||
| (singleItemHeight * itemCount) + dragHandleHeight | ||
| } else 0.dp, |
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.
🛠️ Refactor suggestion
peek 높이가 화면을 초과할 수 있음 — 화면 높이 기준 상한선 필요
아이템 3개만으로도 300.dp * 3이 900dp라 기기 따라 시트를 꽉 채우거나 초과할 수 있어요. 화면의 80~90%로 cap 하세요.
- 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
In app/src/main/java/com/kuit/ourmenu/ui/searchmenu/screen/SearchMenuScreen.kt
around lines 174 to 177, the computed sheetPeekHeight can exceed the device
screen (e.g., 300.dp * 3 = 900.dp); clamp the computed peek height to a
percentage of the screen (80–90%) by reading screen height via
LocalConfiguration (import androidx.compose.ui.platform.LocalConfiguration),
convert configuration.screenHeightDp to Dp, compute a maxPeek =
screenHeightDp.dp * 0.9f (or 0.8f), and set sheetPeekHeight =
min(calculatedPeek, maxPeek) so the bottom sheet never exceeds the screen cap.
| // 검색 시 현재 지도 중심 좌표 사용 | ||
| 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{ |
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
현재 중심 좌표 갱신/조회 사이의 경쟁 조건 가능성
updateCurrentCenter() 호출 직후 getCurrentCoordinates()를 즉시 읽으면 비동기 갱신이 아직 반영되지 않았을 수 있어요. 한 함수에서 좌표를 리턴하도록 합치거나, 콜백/Flow 완료 시점에 이어서 검색 호출하는 방식 권장.
다음 스크립트로 ViewModel 구현을 확인해 동기/비동기 동작을 점검해 주세요.
🏁 Script executed:
#!/bin/bash
# updateCurrentCenter / getCurrentCoordinates 정의 및 사용 맥락 확인
rg -nP -C3 'fun\s+updateCurrentCenter\s*\(|fun\s+getCurrentCoordinates\s*\(' --type=ktLength of output: 115
타입 필터 문제로 기존 스크립트가 실패했습니다. 전체 .kt 파일에서 함수 정의를 검색해 주세요.
🏁 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
In app/src/main/java/com/kuit/ourmenu/ui/searchmenu/screen/SearchMenuScreen.kt
around lines 226 to 246, there's a race where updateCurrentCenter() is
asynchronous so calling getCurrentCoordinates() immediately can return stale
coordinates; change updateCurrentCenter() to a suspend function (or provide a
suspend/returning API that yields the updated center) or expose the updated
center via a Flow/LiveData and move the getMapSearchResult(...) call into the
ViewModel after the center is actually updated so the UI only requests search
results when the ViewModel confirms the new coordinates (i.e., update the
ViewModel to perform updateCurrentCenter and then call getMapSearchResult
internally or return the new center to the caller before invoking
getMapSearchResult).
| 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) | ||
| } | ||
| } | ||
| } | ||
| }, |
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.
🛠️ Refactor suggestion
웹 이동 인텐트 가드 부족 — 스킴 검증 및 예외 처리 추가
빈 문자열 체크만으로는 부족해요. http/https 스킴 확인과 startActivity 실패 대비가 필요합니다.
- 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
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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) | |
| } | |
| } | |
| } | |
| }, | |
| onClick = { | |
| // 네이버 지도에 해당 가게 검색 결과로 이동 | |
| if (activeMapId == null) { | |
| Log.d("SearchMenuScreen", "활성화된 Map ID가 없습니다.") | |
| } else { | |
| scope.launch { | |
| 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") | |
| } | |
| } | |
| } | |
| }, |
| response.onSuccess { result -> | ||
| if (result != null && result.isNotEmpty()) { | ||
| Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: ${result.size}개") | ||
| Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: $result") | ||
| // 검색 결과 저장 | ||
| _searchResult.value = result | ||
|
|
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.
🛠️ Refactor suggestion
검색 결과 전체 객체 로그 출력 지양(개인정보/노이즈)
결과 개수만 로그하세요.
- Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: $result")
+ Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: ${result.size}건")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| response.onSuccess { result -> | |
| if (result != null && result.isNotEmpty()) { | |
| Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: ${result.size}개") | |
| Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: $result") | |
| // 검색 결과 저장 | |
| _searchResult.value = result | |
| response.onSuccess { result -> | |
| if (result != null && result.isNotEmpty()) { | |
| Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: ${result.size}건") | |
| // 검색 결과 저장 | |
| _searchResult.value = result |
🤖 Prompt for AI Agents
In
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt
around lines 269 to 274, the code currently logs the entire search result object
which may contain sensitive data and noisy output; change the logging to only
output the result size (e.g., result.size or result.count()) and a brief
descriptive message, then assign _searchResult.value as before; remove or redact
any direct printing of result contents to avoid leaking personal data.
| // 전체 메뉴 목록을 다시 가져온 후 필터링 | ||
| 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) | ||
| } | ||
| } | ||
| } |
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.
🛠️ Refactor suggestion
전체 메뉴 재조회 과도 + 실패 시 무반응
이미 캐시된 _myMenus가 있으면 재호출을 피하고, 네트워크 실패 시 graceful fallback이 필요합니다.
- // 전체 메뉴 목록을 다시 가져온 후 필터링
- 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)
- }
- }
- }
+ // 캐시 우선, 없으면 1회 조회
+ val allMenus = _myMenus.value?.takeIf { it.isNotEmpty() }
+ ?: mapRepository.getMap().getOrElse {
+ Log.w("SearchMenuViewModel", "전체 메뉴 조회 실패(캐시 사용 불가): ${it.message}")
+ emptyList()
+ } ?: emptyList()
+
+ if (allMenus.isEmpty()) {
+ Log.d("SearchMenuViewModel", "표시할 메뉴가 없습니다.")
+ return@launch
+ }
+
+ _myMenus.value = allMenus.filter { menu ->
+ result.any { searchResult -> searchResult.mapId == menu.mapId }
+ }
+ _activeMapId.value = result.firstOrNull()?.mapId
+ showSearchResultOnMap()
+ _activeMapId.value?.let { getMapDetail(it) }추가로, 이 코루틴은 메인 디스패처입니다. 레포가 IO 디스패처 보장하지 않으면 launch(Dispatchers.IO) 또는 withContext(IO)로 감싸주세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 전체 메뉴 목록을 다시 가져온 후 필터링 | |
| 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) | |
| } | |
| } | |
| } | |
| // 캐시 우선, 없으면 1회 조회 | |
| val allMenus = _myMenus.value | |
| ?.takeIf { it.isNotEmpty() } | |
| ?: mapRepository.getMap().getOrElse { | |
| Log.w("SearchMenuViewModel", "전체 메뉴 조회 실패(캐시 사용 불가): ${it.message}") | |
| emptyList() | |
| } ?: emptyList() | |
| if (allMenus.isEmpty()) { | |
| Log.d("SearchMenuViewModel", "표시할 메뉴가 없습니다.") | |
| return@launch | |
| } | |
| _myMenus.value = allMenus.filter { menu -> | |
| result.any { searchResult -> searchResult.mapId == menu.mapId } | |
| } | |
| _activeMapId.value = result.firstOrNull()?.mapId | |
| showSearchResultOnMap() | |
| _activeMapId.value?.let { getMapDetail(it) } |
🤖 Prompt for AI Agents
In
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt
around lines 275 to 293, avoid unnecessary full list refetch and handle network
failures and threading: first check if _myMenus.value is non-empty and skip
calling mapRepository.getMap() when cached data can satisfy the filter; if you
must call the repository, perform it on IO (wrap the call in
withContext(Dispatchers.IO) or launch(Dispatchers.IO)) and surround it with
try/catch so failures fall back to the existing _myMenus (or surface a
user-facing error) instead of leaving the UI unresponsive; after a successful
fetch, filter as before and set _activeMapId/getMapDetail, but on failure use
the cached list to compute the filtered results or set an error state so the UI
can show a graceful message.
| // 먼저 전체 메뉴를 가져옴 | ||
| val myMenusResponse = mapRepository.getMap() | ||
| myMenusResponse.onSuccess { menus -> | ||
| if (menus != null){ | ||
| val allMenus = menus | ||
| 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}") | ||
| } |
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
getMapMenuDetail 호출 결과를 사용하지 않음(불필요 네트워크 왕복)
현재 menuDetailResponse를 로깅만 하고 버립니다. 제거하거나 히스토리 미존재 시 fallback에 활용하세요.
- val menuDetailResponse = mapRepository.getMapMenuDetail(menuId)
- menuDetailResponse.onSuccess { menuDetail ->
- Log.d("SearchMenuViewModel", "메뉴 상세 조회 성공: $menuDetail")
- // 검색 기록에서 해당 menuId를 가진 항목 찾기
- searchHistory.value?.find { it.menuId == menuId }?.let { historyItem ->
- ...
- }
- }.onFailure {
- Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: ${it.message}")
- }
+ val historyItem = searchHistory.value?.find { it.menuId == menuId }
+ if (historyItem != null) {
+ _myMenus.value = allMenus.filter { it.mapId == historyItem.mapId }
+ _activeMapId.value = historyItem.mapId
+ showSearchResultOnMap()
+ getMapDetail(historyItem.mapId)
+ } else {
+ Log.d("SearchMenuViewModel", "검색 기록에 menuId=$menuId 없음")
+ }- 확인 요청: MapMenuDetailResponse에 mapId가 존재한다면, 히스토리 없을 때 menuDetail.mapId로 활성화하는 분기를 추가해 주세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the MapRepository to inspect getMapMenuDetail signature
rg -nP "interface MapRepository" -n .
# Find the implementation to see return type
rg -nP "class .*MapRepositoryImpl" -n .
# Locate the MapMenuDetailResponse data class
rg -nP "data class MapMenuDetailResponse" -n .Length of output: 59
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# getMapMenuDetail 선언 위치 및 반환 타입 확인
rg -n --iglob '*.kt' "fun getMapMenuDetail" -C3
# MapMenuDetailResponse 데이터 클래스 및 필드 확인
rg -n --iglob '*.kt' "data class MapMenuDetailResponse" -C3
# 혹시 명칭이 다르면 MapMenuDetail 키워드로도 탐색
rg -n --iglob '*.kt' "MapMenuDetail" -C3Length of output: 9379
위 응답에서 MapMenuDetailResponse에 mapId가 없는 것처럼 보입니다. 클래스 정의를 확인해 주세요:
🏁 Script executed:
#!/bin/bash
sed -n '1,200p' app/src/main/java/com/kuit/ourmenu/data/model/map/response/MapMenuDetailResponse.ktLength of output: 914
getMapMenuDetail 결과 활용 및 히스토리 미존재 시 fallback 분기 추가
menuDetailResponse에서 mapId를 꺼내, searchHistory에 해당 메뉴가 없을 때에도 그 mapId로 _activeMapId 설정·_myMenus 필터링·showSearchResultOnMap()·getMapDetail() 호출 로직을 구현하세요.
🤖 Prompt for AI Agents
In
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt
around lines 338 to 366, the current logic only updates _activeMapId, _myMenus,
showSearchResultOnMap(), and getMapDetail() when a matching entry exists in
searchHistory; update the onSuccess block for menuDetailResponse to extract the
mapId from menuDetail (guarding for null), then attempt to find a historyItem by
that menuId and if found behave as before, otherwise use menuDetail.mapId as a
fallback: set _activeMapId to that mapId, set _myMenus to allMenus filtered by
that mapId, call showSearchResultOnMap(), and call getMapDetail(mapId); ensure
you still log success/failure and avoid NPEs by checking menuDetail/mapId
non-null before using them.
| 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}") | ||
| "" | ||
| } | ||
| ) |
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.
🛠️ 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}")
""
}
)- 선택:
_menusOnPin.value?.firstOrNull()?.storeTitle우선 사용 후 없을 때만 네트워크 호출.
🤖 Prompt for AI Agents
In
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt
around lines 397 to 413, the function builds a web search URL without
URL-encoding store titles and always calls the repository even if data is
already present; change it to first try
_menusOnPin.value?.firstOrNull()?.storeTitle and use that when non-null/blank to
avoid the network request, otherwise call mapRepository.getMapDetail(mapId);
before concatenating to baseUrl, URL-encode the storeTitle (e.g., via
URLEncoder.encode(title, "UTF-8")) and handle null/blank safely to return an
empty string on failure.
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.
Actionable comments posted: 1
♻️ Duplicate comments (3)
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt (3)
271-273: 로그에 전체 결과 객체 출력 지양(개인정보/노이즈)과거 코멘트와 동일 이슈입니다. 결과 수만 남기세요.
다음 패치 제안:
- Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: $result") + Log.d("SearchMenuViewModel", "등록 메뉴 정보 조회 성공: ${result.size}건")
275-297: 검색 후 전체 목록 재조회: 캐시 우선 + 실패시 graceful fallback + IO 분리불필요한 네트워크 왕복과 중첩 콜백입니다. 캐시(_myMenus) 우선 사용하고, 필요 시 한 번만 조회하세요. 히스토리 반영 호출도 실패 로그 포함해 IO에서 처리 권장.
- // 전체 메뉴 목록을 다시 가져온 후 필터링 - 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}") - } - } - } + // 캐시 우선, 없으면 1회 조회(+IO) + 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 + } + + _myMenus.value = allMenus.filter { menu -> + result.any { searchResult -> searchResult.mapId == menu.mapId } + } + _activeMapId.value = result.firstOrNull()?.mapId + showSearchResultOnMap() + _activeMapId.value?.let { getMapDetail(it) } + + // 서버 히스토리 반영(성공/실패 로그, IO) + result.firstOrNull()?.menuId?.let { id -> + withContext(Dispatchers.IO) { + mapRepository.getMapMenuDetail(id) + .onFailure { Log.w("SearchMenuViewModel", "검색 기록 반영 실패(menuId=$id): ${it.message}") } + } + }
401-419: 네이버 검색 쿼리: URL 인코딩 누락 + 캐시 우선 사용
- 공백/특수문자 인코딩 필요.
- 이미
_menusOnPin에 제목이 있으면 네트워크 재호출 불필요.- 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}") - "" - } - ) - } + suspend fun getWebSearchQuery(mapId: Long): String { + val baseUrl = "https://map.naver.com/p/search/" + // 캐시 우선 + val cachedTitle = _menusOnPin.value?.firstOrNull()?.storeTitle?.takeIf { it.isNotBlank() } + val title = cachedTitle ?: withContext(Dispatchers.IO) { + mapRepository.getMapDetail(mapId).getOrElse { + Log.d("SearchMenuViewModel", "메뉴 상세 조회 실패: ${it.message}") + emptyList() + }.firstOrNull()?.storeTitle + } + return title?.let { + val encoded = java.net.URLEncoder.encode( + it, + java.nio.charset.StandardCharsets.UTF_8.name() + ) + baseUrl + encoded + } ?: "" + }
🧹 Nitpick comments (3)
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt (3)
378-393: 카메라 이동 대상: 첫 번째 → 현재 중심과 가장 가까운 결과로UX 측면에서 현재 뷰 중심과 가장 가까운 결과로 이동이 자연스럽습니다. 로그도 “N건” 정도만 남기면 충분.
- // 첫 번째 검색 결과로 카메라 이동 TODO: 현재 위치랑 가까운 결과로 이동 - moveCamera(menus[0].mapY, menus[0].mapX) + // 현재 중심과 가장 가까운 결과로 이동 + val target = currentCenter.value?.let { c -> + menus.minByOrNull { + val dy = it.mapY - c.latitude + val dx = it.mapX - c.longitude + dy * dy + dx * dx + } + } ?: menus.first() + moveCamera(target.mapY, target.mapX)
395-399: clearActiveMapId 시 시트 데이터도 비우기 권장활성화 해제 후 바텀시트에 이전 핀의 메뉴가 남아있을 수 있습니다. 함께 초기화하면 혼동 방지됩니다.
fun clearActiveMapId() { _activeMapId.value = null refreshMarkers() + _menusOnPin.value = emptyList() }
204-223: setOnLabelClickListener를 라벨 추가 시마다 재설정하지 마세요마커 추가 루프마다 리스너를 재등록하면 마지막 등록만 유효하고 불필요한 작업이 반복됩니다. 초기화 시 한 번만 설정하고, 클릭 시 좌표/태그로 식별하세요.
예시(초기화 시 1회 설정):
// initializeMap 내부 어딘가에서 1회 mapController.kakaoMap.value?.setOnLabelClickListener { _, _, label -> val lat = label.position.latitude val lng = label.position.longitude _myMenus.value?.find { it.mapY == lat && it.mapX == lng }?.let { m -> _activeMapId.value = m.mapId refreshMarkers() getMapDetail(m.mapId) moveCamera(lat, lng) } true }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt (3)
app/src/main/java/com/kuit/ourmenu/ui/addmenu/viewmodel/AddMenuViewModel.kt (4)
showSearchResultOnMap(247-260)clearMarkers(171-175)addMarker(147-168)moveCamera(114-121)app/src/main/java/com/kuit/ourmenu/data/service/MapService.kt (2)
getMapDetail(16-57)getMapDetail(18-21)app/src/main/java/com/kuit/ourmenu/data/repository/MapRepository.kt (1)
getMapDetail(12-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: ci-build
| // 먼저 전체 메뉴를 가져옴 | ||
| 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}") | ||
| } |
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
getMapMenuDetail: 결과 미사용 + 히스토리 미존재 fallback 부재 + 캐시 미활용
- menuDetail 결과를 로그만 하고 활용하지 않습니다.
- searchHistory에 없을 때를 대비한 fallback(mapId) 분기가 없습니다.
- 매번 전체 목록 재조회 대신 캐시 우선.
- // 먼저 전체 메뉴를 가져옴
- 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}") }검증 필요:
- 만약
mapId가 없다면, 상세 응답에서 mapId에 해당하는 필드를 알려주세요. 그에 맞추어detailResult.getOrNull()?.<field>로 교체하겠습니다.
🏁 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 로직 적용
- SearchMenuViewModel.kt (342–371)에서 myMenus 캐시 사용 후, searchHistory 미존재 시
detailResult.getOrNull()?.mapId로 fallback하는 제안 로직을 적용하세요. MapMenuDetailResponse에@SerialName("mapId") val mapId: Long필드가 있어 해당 필드를 바로 사용할 수 있습니다 (app/src/main/java/com/kuit/ourmenu/data/model/map/response/MapMenuDetailResponse.kt).
🤖 Prompt for AI Agents
In
app/src/main/java/com/kuit/ourmenu/ui/searchmenu/viewmodel/SearchMenuViewModel.kt
around lines 342 to 371, update the logic that handles myMenus and menuDetail so
it first uses cached myMenus (as already fetched) and when no matching
searchHistory item exists, fall back to the mapId from the menu detail response
(detailResult.getOrNull()?.mapId) to filter and activate the map; specifically,
after menuDetailResponse.onSuccess, find a historyItem as now, and if null use
menuDetail?.mapId (from MapMenuDetailResponse) as fallback to set _myMenus
(filter by that mapId), set _activeMapId, call showSearchResultOnMap(), and
getMapDetail(mapId); preserve existing logging and onFailure handlers.
ikseong00
left a comment
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.
LGTM :)
| BackHandler(enabled = showSearchBackground || showBottomSheet) { | ||
| if (showSearchBackground) { | ||
| if (searchBarFocused) focusManager.clearFocus() | ||
| searchActionDone = false | ||
| showSearchBackground = false | ||
| searchText = "" | ||
| } else if (showBottomSheet) { | ||
| showBottomSheet = false | ||
| viewModel.clearActiveMapId() | ||
| } |
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.
👍
🚀 이슈번호
✏️ 변경사항
📷 스크린샷
Screen_Recording_20250829_130624_OurMenu.mp4
✍️ 사용법
🎸 기타
Summary by CodeRabbit
신규 기능
개선
기타