Conversation
- WitnessReportViewModel에서 TODO 주석 제거 - MissingReportViewModel에서 TODO 주석 및 사용하지 않는 코드 주석 제거
- 다이얼로그 상단 여백 수정 - 버튼 텍스트 색상 흰색으로 수정 - 기타 레이아웃 속성 수정
- 이미지 추가 시 원하는 위치에 추가하도록 수정 - 이미지 추가 버튼 클릭 시 해당 페이지 정보 전달 - ViewModel에서 페이지 정보를 받아 이미지 리스트 업데이트 - 이미지 삭제 아이콘 추가
- 지도 카메라 이동 완료 시 현재 위치의 주소 업데이트 - `snapshotFlow`를 사용하여 카메라 이동 상태 감지 및 `distinctUntilChanged` `filter`로 이벤트 최적화 - `GetAddressUseCase`를 사용하여 위경도 좌표를 주소로 변환 - `ReportLocationComponent`의 지도 높이 조정 - `MissingReportScreen` 및 `WitnessReportScreen` 버튼 활성화 조건에서 `speciesType` 제거
- NaverService에서 사용하던 ReverseGeocode API 엔드포인트를 ApiConstraints로 이동 - ApiConstraints에 Report, Breed 관련 엔드포인트 추가
- 주소로 좌표를 검색하는 GetLatLngUseCase 추가 - ReportRepository에 getLatLng 함수 추가 - Naver API 관련 GeocodeResponseDto 및 매퍼 함수 추가 - 목격/실종 신고 ViewModel에 주소 검색 시 좌표 업데이트 로직 추가
- 실종 신고 시 `missingDate`를 `yyyy-MM-dd` 형식으로 변환하여 전송하도록 수정 - `furColors`를 쉼표로 구분된 문자열로 변환하여 전송하도록 수정 - 실종 신고 완료 시 `FinishDialog`를 표시하는 로직 제거
- `fragment_report_camera.xml`에서 촬영 버튼의 텍스트 색상을 흰색으로 수정
- 이미지 등록 시 5개가 넘으면 Toast 메시지 출력 - 이미지 등록 개수 제한 로직 추가
WalkthroughNaver 지도 API 연동 구조를 개편하고(정/역지오코딩 추가), 신고 이미지 업로드 응답 스키마를 변경했습니다. 품종/성별/색상 도메인 모델을 정리하고 UI 컴포넌트 시그니처를 업데이트했으며, 목격/실종 신고 ViewModel과 화면에 검색·로딩·지도 카메라 연동을 추가했습니다. 다수 레이아웃 리소스의 배경 라운드를 교체했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant UI as Screen(Report)
participant VM as ViewModel
participant UC1 as GetAddressUseCase
participant UC2 as GetLatLngUseCase
participant Repo as ReportRepository
participant DS as NaverRemoteDataSource
participant S as NaverService
participant API as Naver Maps API
rect rgba(200,220,255,0.3)
note right of UI: 카메라 이동 종료(디바운스)
U->>UI: 지도 이동/정지
UI->>VM: OnCameraTargetMoved(lat,lng)
VM->>UC1: invoke(lat,lng)
UC1->>Repo: getAddress(lat,lng)
Repo->>DS: getAddress(coords)
DS->>S: GET /REVERSE_GEOCODE
S->>API: Reverse Geocode
API-->>S: ReverseGeocodeResponseDto
S-->>DS: ReverseGeocodeResponseDto
DS-->>Repo: DTO
Repo-->>UC1: Result<AddressData>
UC1-->>VM: Result<AddressData>
VM-->>UI: 상태 업데이트(주소)
end
rect rgba(220,255,200,0.3)
note right of UI: 주소 직접 입력 처리
U->>UI: 주소 입력
UI->>VM: updateAddress(address)
VM->>UC2: invoke(address)
UC2->>Repo: getLatLng(address)
Repo->>DS: getLatLng(address)
DS->>S: GET /GEOCODE
S->>API: Geocode
API-->>S: GeocodeResponseDto
S-->>DS: GeocodeResponseDto
DS-->>Repo: DTO
Repo-->>UC2: Result<LatLngData>
UC2-->>VM: Result<LatLngData>
VM-->>UI: 카메라 타겟 갱신
end
sequenceDiagram
autonumber
actor U as User
participant UI as ReportImageComponent
participant VM as ViewModel
participant UC as UploadImagesUseCase
participant Repo as ReportRepository
participant RS as ReportService
participant API as Backend
U->>UI: 신고 완료 버튼
UI->>VM: 제출 이벤트(이미지 Uri 목록)
VM->>UC: 업로드 요청
UC->>Repo: uploadImages(parts)
Repo->>RS: POST /api/v2/images/upload
RS->>API: 업로드
API-->>RS: BaseResponse<ImageUploadResponseDto(urls)>
RS-->>Repo: Response
Repo-->>UC: Result<List<String>>
UC-->>VM: 이미지 URL 리스트
VM-->>VM: 신고 본문 생성
VM-->>API: 신고 API 호출(별도)
API-->>VM: 결과
VM-->>UI: 성공/실패 상태 반영
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✨ 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (26)
app/src/main/java/com/example/findu/presentation/util/extension/CharExt.kt (4)
58-60: 입력 포맷 주석 보완 제안현재 구현은 요일 표기(예: " (화)")가 포함되면 파싱에 실패합니다. 주석에 허용 포맷을 명확히 추가하거나(요일/공백/0‑패딩 허용 여부) 아래 수정안처럼 구현을 범용화하는 편이 안전합니다.
74-75: LocalDate 사용이 더 명확합니다날짜만 필요하므로
LocalDateTime(...).date대신LocalDate(year, month, day)가 직관적입니다. 상단에kotlinx.datetime.LocalDateimport 추가 후 아래처럼 간결화하세요.- val parsed = if (year != null && month != null && day != null) { - runCatching { - LocalDateTime(year, month, day, 0, 0).date.toString() - }.getOrNull() - } else null + val parsed = if (year != null && month != null && day != null) { + runCatching { + LocalDate(year, month, day).toString() + }.getOrNull() + } else null
62-62: 사소한:this.생략 가능Kotlin에서는 수신 객체 참조가 불필요합니다.
this.isBlank()→isBlank().
61-76: 테스트 케이스 추가 제안다음 입력들에 대한 단위 테스트를 추가해 회귀를 방지해 주세요:
- "2023년 8월 5일"
- "2023년08월05일"
- "2023년 08월 05일 (토)"
- 공백/빈 문자열(시스템 타임존 기준 오늘 날짜 반환)
원하시면 테스트 스캐폴딩을 만들어 드릴게요.
app/src/main/res/layout/fragment_report_camera.xml (1)
24-24: 캡처 버튼 텍스트 색상 화이트 지정 OK + 상태별 가독성 제안비활성/프레스 상태에서도 대비 확보를 위해 텍스트 ColorStateList 사용을 권장합니다.
아래처럼 상태 색상으로 교체해 주세요.
- android:textColor="@color/white" + android:textColor="@color/btn_report_camera_text"res/color/btn_report_camera_text.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="false" android:color="@color/gray3"/> <item android:state_pressed="true" android:color="@color/white"/> <item android:color="@color/white"/> </selector>app/src/main/res/layout/dialog_report_finished.xml (1)
57-59: 버튼 텍스트 White 고정 — 다크/비활성 상태 대비 개선 제안bg_button_report_dialog의 바탕색과 상태(pressed/disabled)에 따라 대비가 깨질 수 있어요. 테마 속성/셀렉터로 전환을 권장합니다.
아래처럼 텍스트 컬러를 테마 의존으로 바꾸면 라이트/다크/다이내믹컬러 대응이 쉬워집니다(배경이 colorPrimary 톤일 때 유효).
- android:textColor="@color/white" /> + android:textColor="?attr/colorOnPrimary" />배경이 커스텀 드로어블 색이면, 상태 셀렉터(@color/btn_on_primary_text 등)로 disabled/pressed 대비도 함께 정의하는 걸 추천합니다. 원하시면 셀렉터 리소스 초안 만들어 드릴게요.
Also applies to: 68-70
app/src/main/res/layout/dialog_home_find.xml (1)
6-6: 배경 rd15로 통일 OK시각적 일관성 향상. 향후에는 코너 반경을 드로어블 개별 관리 대신 테마의 ShapeAppearance로 중앙집중화하는 것도 고려해 보세요.
app/src/main/java/com/example/findu/presentation/ui/report/component/ReportLocationComponent.kt (5)
98-101: 매직 넘버(160.dp) 대신 파라미터/리소스로 치환 권장맵 높이 고정값은 화면/회전/분할뷰에서 유연하지 않습니다. 기본값은 유지하되 파라미터나 dimen 리소스로 뺄 것을 제안합니다.
변경 제안(해당 범위 내):
- .height(160.dp), + .height(mapHeight),시그니처 업데이트(범위 밖 추가 변경):
-fun ReportLocationComponent( +fun ReportLocationComponent( modifier: Modifier = Modifier, address: String, cameraPositionState: CameraPositionState, nearPlace: TextFieldState, onAddressClick: () -> Unit = { }, dismissKeyboard: () -> Unit = { }, + mapHeight: Dp = 160.dp, ) {또는 dimen 리소스 사용:
- .height(160.dp), + .height(dimensionResource(R.dimen.report_location_map_height)),
114-118: 핀 아이콘에 접근성 설명 추가 권장현재
contentDescription = null이라 스크린리더 사용자는 핀의 의미를 파악하기 어렵습니다. “선택된 위치” 등 간략한 설명을 제공해 주세요.변경 제안:
- Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_location_pin), - contentDescription = null, - tint = Color.Unspecified, - ) + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_location_pin), + contentDescription = stringResource(R.string.report_location_pin_description), + tint = Color.Unspecified, + )(문자열 리소스
report_location_pin_description추가 필요)
76-95: 주소 선택 Row에 명시적 역할/시맨틱 부여로 a11y 개선Row 전체가 클릭 가능하지만 버튼 역할/라벨이 없어 보조공학에서 모호합니다. Role과 라벨을 추가해 주세요.
변경 제안:
- Row( - modifier = Modifier.clickable { onAddressClick() }, + Row( + modifier = Modifier + .semantics { + role = Role.Button + contentDescription = stringResource(R.string.report_location_select_address) + } + .clickable(role = Role.Button, onClick = onAddressClick),필요 import:
import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.Role(아이콘은 장식 용도이므로
contentDescription = null유지 OK)
50-59: 맵 영역 라운딩 누수 가능성외곽
Column에는 라운드 보더가 있으나 맵 컨테이너(Box)는 clip이 없어 모서리 시각 일관성이 깨질 수 있습니다. Box에clip(RoundedCornerShape(...))추가를 고려해 주세요.예:
Box( modifier = Modifier .fillMaxWidth() .height(mapHeight) .clip(RoundedCornerShape(12.dp)), contentAlignment = Alignment.Center ) { ... }Also applies to: 97-101
131-139: Preview에서도 remember 사용 권장
CameraPositionState()직접 생성 대신rememberCameraPositionState를 사용하면 Preview에서도 실제 사용 패턴과 일치합니다.예:
@Preview(showBackground = true) @Composable private fun ReportLocationComponentPreview() { FindUTheme { val cameraState = rememberCameraPositionState() ReportLocationComponent( address = "서울특별시 강남구 테헤란로 123", nearPlace = TextFieldState(""), cameraPositionState = cameraState, ) } }필요 import:
import com.naver.maps.map.compose.rememberCameraPositionStateapp/src/main/java/com/example/findu/data/dataremote/model/request/AiDetectionRequestDto.kt (1)
8-9: 대용량 Base64 전송 주의(압축·크기·로그 마스킹)
- 서버 요청 바디 제한(예: 5–10MB) 확인 및 필요 시 안내/에러 처리.
- 서버가 Content-Encoding: gzip 수용 시, 요청 바디 gzip 압축 고려(OkHttp 네트워크 인터셉터).
- 로그/분석 이벤트에서 base64Image 값은 반드시 마스킹/비저장 처리.
app/src/main/java/com/example/findu/data/mapper/torequest/AiDetectionRequestDtoMapper.kt (1)
6-6: 입력 검증(옵션): 빈 문자열 방지빈 값이 내려가면 400/422가 날 수 있어요. 여기서 가볍게 가드해도 좋습니다.
다음과 같이 변경 제안:
-fun String.toAiDetectionRequest() = AiDetectionRequestDto( - base64Image = this -) +fun String.toAiDetectionRequest(): AiDetectionRequestDto { + require(isNotBlank()) { "base64Image must not be blank" } + return AiDetectionRequestDto(base64Image = this) +}app/src/main/java/com/example/findu/domain/model/breed/BreedData.kt (1)
9-29: Breed 계층: 중복 필드(id/breedId, name/breedName) 제거 제안현재 상위
Breed의id/name와 하위의breedId/breedName가 중복돼 유지보수 리스크가 큽니다. 단일 소스로 통일하는 구조가 바람직합니다. 아래처럼 sealed interface로 단순화하면 중복 없이 명확해집니다.-sealed class Breed( - val name: String, - val id: Int = 0, -) { - data class DogBreed( - val breedId: Int = 0, - val breedName: String, - val species: SpeciesType, - ) : Breed(breedName, breedId) - - data class CatBreed( - val breedId: Int = 0, - val breedName: String, - val species: SpeciesType, - ) : Breed(breedName, breedId) - - data class EtcBreed( - val breedId: Int = 0, - val breedName: String, - val species: SpeciesType, - ) : Breed(breedName, breedId) -} +sealed interface Breed { + val id: Int + val name: String + val species: SpeciesType + + data class DogBreed( + override val id: Int = 0, + override val name: String, + override val species: SpeciesType, + ) : Breed + + data class CatBreed( + override val id: Int = 0, + override val name: String, + override val species: SpeciesType, + ) : Breed + + data class EtcBreed( + override val id: Int = 0, + override val name: String, + override val species: SpeciesType, + ) : Breed +}변경 영향 범위가 넓을 수 있으니, 적용 시 컴파일 타임에서 대부분 드러날 겁니다. 필요하시면 리팩터링 스텝을 쪼개는 안도 도와드릴게요.
app/src/main/java/com/example/findu/presentation/ui/report/navigation/WitnessReportNavHost.kt (1)
25-26: NavHost 트랜지션 설정fadeIn/fadeOut 트랜지션이 duration 0으로 설정되어 즉시 전환됩니다.
트랜지션 효과를 원한다면 duration을 조정하는 것을 고려해보세요:
- enterTransition = { fadeIn(tween(0)) }, - exitTransition = { fadeOut(tween(0)) }, + enterTransition = { fadeIn(tween(300)) }, + exitTransition = { fadeOut(tween(300)) },app/src/main/java/com/example/findu/presentation/ui/report/navigation/MissingReportNavHost.kt (1)
25-26: WitnessReportNavHost와 동일한 트랜지션 설정두 NavHost가 동일한 트랜지션 설정을 사용하여 일관성이 있습니다.
공통 트랜지션 상수를 만들어서 코드 중복을 줄이는 것을 고려해보세요:
// NavigationTransitions.kt object NavigationTransitions { val DEFAULT_ENTER = fadeIn(tween(0)) val DEFAULT_EXIT = fadeOut(tween(0)) }app/src/main/java/com/example/findu/data/mapper/toDomain/report/NaverResponseDtoMapper.kt (2)
14-17: 배열 접근 시 안전성 확인 필요
addresses[0]에 직접 접근하고 있는데, 빈 배열인 경우IndexOutOfBoundsException이 발생할 수 있습니다.안전한 처리를 위해 다음과 같이 수정하는 것을 고려해보세요:
-fun GeocodeResponseDto.toDomain() = LatLngData( - addresses[0].y.toDouble(), - addresses[0].x.toDouble() -) +fun GeocodeResponseDto.toDomain() = addresses.firstOrNull()?.let { + LatLngData(it.y.toDouble(), it.x.toDouble()) +} ?: throw IllegalStateException("No address found in geocode response")
8-12: ReverseGeocode 응답 매핑 확인
results[0]에 대한 접근도 빈 배열인 경우를 처리하지 않고 있습니다.다음과 같이 안전한 처리를 추가하는 것을 권장합니다:
-fun ReverseGeocodeResponseDto.toDomain() = AddressData( - with(results[0]) { +fun ReverseGeocodeResponseDto.toDomain() = results.firstOrNull()?.let { result -> + AddressData(with(result) { region.area1.name + " " + region.area2.name + " " + region.area3.name + " " + region.area4.name + " " + land.name + " " + land.number1 }) +} ?: AddressData("")app/src/main/java/com/example/findu/presentation/ui/report/screen/WitnessReportScreen.kt (1)
57-57: 중복 import 제거 필요
kotlinx.coroutines.time.debounce가 Line 57에 import되어 있는데, Line 53의kotlinx.coroutines.flow.debounce와 중복됩니다.-import kotlinx.coroutines.time.debounceapp/src/main/java/com/example/findu/data/dataremote/model/response/report/ReverseGeocodeResponseDto.kt (2)
8-57: 파일명과 클래스명이 일치하지 않습니다파일명은
ReverseGeocodeResponseDto.kt인데 파일 내에GeocodeResponseDto와ReverseGeocodeResponseDto두 개의 클래스가 정의되어 있습니다. 코드 구조를 명확히 하기 위해 각 DTO를 별도 파일로 분리하는 것이 좋겠습니다.-// ReverseGeocodeResponseDto.kt 파일에서 -@Serializable -data class GeocodeResponseDto( - // ... -) - -@Serializable -data class ReverseGeocodeResponseDto( - // ... -) +// GeocodeResponseDto.kt 파일로 분리 +// ReverseGeocodeResponseDto.kt는 ReverseGeocodeResponseDto만 포함
19-19: 타입 이름 오타:Addresse→Address
Addresse는 영어 철자가 틀렸습니다. 올바른 철자인Address로 수정해주세요.- data class Addresse( + data class Address(app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/MissingReportViewModel.kt (1)
184-196: 브리드 필터링 로직 최적화 가능브리드 필터링 로직이
WitnessReportViewModel과 완전히 동일합니다. 공통 유틸리티 함수로 추출하면 코드 중복을 제거할 수 있습니다.// BreedFilterUtil.kt 또는 확장 함수로 추출 fun filterBreedsBySpeciesAndSearch( speciesType: SpeciesType, breedData: BreedData, searchText: String ): List<Breed> { val breedList = when (speciesType) { SpeciesType.DOG -> breedData.dogBreedList SpeciesType.CAT -> breedData.catBreedList SpeciesType.ETC -> breedData.etcBreedList } return breedList.filter { searchText.isBlank() || it.name.contains(searchText, ignoreCase = true) } }app/src/main/java/com/example/findu/presentation/ui/report/component/animalinfo/ReportBreedComponent.kt (2)
74-79: 디바운스 시간을 설정 가능하게 만들기검색 디바운스가 300ms로 하드코딩되어 있습니다. 성능 튜닝이나 UX 개선을 위해 파라미터로 받거나 상수로 정의하는 것이 좋겠습니다.
+private const val SEARCH_DEBOUNCE_MILLIS = 300L @OptIn(FlowPreview::class) @Composable fun ReportBreedComponent( // ... ) { LaunchedEffect(breedState) { snapshotFlow { breedState.text.toString() } .distinctUntilChanged() - .debounce(300) + .debounce(SEARCH_DEBOUNCE_MILLIS) .collect { onSearchFieldChange() } }
200-200: heightIn의 단위 변환 오류
containerHeight가 픽셀 단위인데.dp로 변환하고 있습니다. 이는 의도한 것과 다른 높이가 될 수 있습니다.- .heightIn(max = containerHeight.dp * 8), + .heightIn(max = (containerHeight * 8).dp),아니면 LocalDensity를 사용하여 정확한 변환을 해야 합니다:
val density = LocalDensity.current .heightIn(max = with(density) { (containerHeight * 8).toDp() })app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt (1)
294-316: breed 검색 로직 개선 필요
contains를 사용하여 품종을 찾고 있는데, 부분 문자열 매칭으로 인해 잘못된 품종이 선택될 수 있습니다. 예를 들어 "Retriever"를 검색하면 "Golden Retriever"와 "Labrador Retriever" 모두 매칭됩니다.- breedName = _uiState.value.breedList.dogBreedList.find { - it.name.contains(detectedName) - }?.breedName ?: AI_DETECT_FAIL_TEXT, + breedName = _uiState.value.breedList.dogBreedList.find { + it.name.equals(detectedName, ignoreCase = true) + }?.breedName ?: _uiState.value.breedList.dogBreedList.find { + it.name.contains(detectedName, ignoreCase = true) + }?.breedName ?: AI_DETECT_FAIL_TEXT,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (55)
app/src/main/java/com/example/findu/data/dataremote/datasource/NaverRemoteDataSource.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/datasource/ReportRemoteDataSource.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/NaverRemoteDataSourceImpl.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/ReportRemoteDataSourceImpl.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/request/AiDetectionRequestDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/response/report/ImageUploadResponseDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/model/response/report/ReverseGeocodeResponseDto.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/service/NaverService.kt(1 hunks)app/src/main/java/com/example/findu/data/dataremote/service/ReportService.kt(2 hunks)app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/toDomain/GptResponseDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/toDomain/report/NaverResponseDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/toDomain/report/ReportResponseDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/torequest/AiDetectionRequestDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/mapper/torequest/ReportRequestDtoMapper.kt(1 hunks)app/src/main/java/com/example/findu/data/repositoryimpl/ReportRepositoryImpl.kt(4 hunks)app/src/main/java/com/example/findu/di/ServiceModule.kt(1 hunks)app/src/main/java/com/example/findu/di/UseCaseModule.kt(3 hunks)app/src/main/java/com/example/findu/domain/model/breed/BreedData.kt(1 hunks)app/src/main/java/com/example/findu/domain/model/breed/SpeciesType.kt(1 hunks)app/src/main/java/com/example/findu/domain/model/report/FurColorType.kt(1 hunks)app/src/main/java/com/example/findu/domain/model/report/Gender.kt(1 hunks)app/src/main/java/com/example/findu/domain/model/report/LatLngData.kt(1 hunks)app/src/main/java/com/example/findu/domain/model/report/MissingReportData.kt(1 hunks)app/src/main/java/com/example/findu/domain/model/report/WitnessReportData.kt(1 hunks)app/src/main/java/com/example/findu/domain/repository/report/ReportRepository.kt(2 hunks)app/src/main/java/com/example/findu/domain/usecase/report/GetLatLngUseCase.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/common/LoadingIndicator.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/report/component/ReportImageComponent.kt(12 hunks)app/src/main/java/com/example/findu/presentation/ui/report/component/ReportLocationComponent.kt(2 hunks)app/src/main/java/com/example/findu/presentation/ui/report/component/animalinfo/ReportBreedComponent.kt(8 hunks)app/src/main/java/com/example/findu/presentation/ui/report/component/animalinfo/ReportSpeciesComponent.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/report/component/missing/MissingAnimalInfoComponent.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/report/component/missing/ReportGenderComponent.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/report/component/witness/WitnessAnimalInfoComponent.kt(1 hunks)app/src/main/java/com/example/findu/presentation/ui/report/navigation/MissingReportNavHost.kt(2 hunks)app/src/main/java/com/example/findu/presentation/ui/report/navigation/WitnessReportNavHost.kt(2 hunks)app/src/main/java/com/example/findu/presentation/ui/report/screen/MissingReportScreen.kt(7 hunks)app/src/main/java/com/example/findu/presentation/ui/report/screen/ReportAnimalInfoScreen.kt(6 hunks)app/src/main/java/com/example/findu/presentation/ui/report/screen/WitnessReportScreen.kt(7 hunks)app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/MissingReportViewModel.kt(11 hunks)app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt(10 hunks)app/src/main/java/com/example/findu/presentation/util/UriUtil.kt(2 hunks)app/src/main/java/com/example/findu/presentation/util/extension/CharExt.kt(2 hunks)app/src/main/res/drawable/bg_report_dialog_rd15.xml(1 hunks)app/src/main/res/layout/dialog_home_find.xml(1 hunks)app/src/main/res/layout/dialog_home_report.xml(1 hunks)app/src/main/res/layout/dialog_home_report_detail.xml(1 hunks)app/src/main/res/layout/dialog_logout.xml(1 hunks)app/src/main/res/layout/dialog_my_delete_history.xml(1 hunks)app/src/main/res/layout/dialog_my_withdrawal.xml(1 hunks)app/src/main/res/layout/dialog_report_finished.xml(4 hunks)app/src/main/res/layout/dialog_report_location.xml(1 hunks)app/src/main/res/layout/dialog_report_navigation.xml(1 hunks)app/src/main/res/layout/fragment_report_camera.xml(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
app/src/main/java/com/example/findu/presentation/ui/report/screen/MissingReportScreen.kt (1)
app/src/main/java/com/example/findu/presentation/ui/common/LoadingIndicator.kt (1)
LoadingIndicatorDialog(25-37)
app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/MissingReportViewModel.kt (4)
app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt (13)
navigateUp(437-441)setImageDialogVisible(467-485)deleteImage(510-517)navigateToAddressSearch(425-429)updateAddress(331-350)updateAddress(404-423)updateBreed(395-402)updateBreedResult(184-196)setDateBottomSheetVisible(391-393)updateDate(382-389)updateFurColor(352-364)getImageUrls(233-249)showFinishDialog(431-435)app/src/main/java/com/example/findu/presentation/ui/report/MissingReportFragment.kt (1)
navigateToAddressSearch(227-230)app/src/main/java/com/example/findu/presentation/ui/report/WitnessReportFragment.kt (1)
navigateToAddressSearch(221-224)app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/LocationViewModel.kt (1)
getAddressUseCase(13-39)
app/src/main/java/com/example/findu/presentation/ui/report/screen/ReportAnimalInfoScreen.kt (2)
app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt (1)
clearFocus(318-322)app/src/main/java/com/example/findu/presentation/ui/report/component/animalinfo/ReportBreedComponent.kt (1)
ReportBreedComponent(59-166)
app/src/main/java/com/example/findu/presentation/ui/report/screen/WitnessReportScreen.kt (1)
app/src/main/java/com/example/findu/presentation/ui/common/LoadingIndicator.kt (1)
LoadingIndicatorDialog(25-37)
app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt (11)
app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/MissingReportViewModel.kt (10)
navigateUp(378-382)setImageDialogVisible(409-427)deleteImage(452-459)navigateToAddressSearch(366-370)updateAddress(272-291)updateAddress(345-364)updateBreed(336-343)updateBreedResult(184-196)getImageUrls(236-254)showFinishDialog(372-376)app/src/main/java/com/example/findu/presentation/ui/report/MissingReportFragment.kt (1)
navigateToAddressSearch(227-230)app/src/main/java/com/example/findu/presentation/ui/report/WitnessReportFragment.kt (1)
navigateToAddressSearch(221-224)app/src/main/java/com/example/findu/data/repositoryimpl/ReportRepositoryImpl.kt (1)
postWitnessReport(60-65)app/src/main/java/com/example/findu/domain/repository/report/ReportRepository.kt (1)
postWitnessReport(23-23)app/src/main/java/com/example/findu/data/dataremote/datasource/ReportRemoteDataSource.kt (1)
postWitnessReport(15-15)app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/ReportRemoteDataSourceImpl.kt (1)
postWitnessReport(22-23)app/src/main/java/com/example/findu/data/dataremote/service/ReportService.kt (1)
postWitnessReport(28-31)app/src/main/java/com/example/findu/domain/model/breed/SpeciesType.kt (1)
fromString(12-12)app/src/main/java/com/example/findu/domain/model/report/FurColorType.kt (1)
fromString(17-17)app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/LocationViewModel.kt (1)
getAddressUseCase(13-39)
🔇 Additional comments (74)
app/src/main/java/com/example/findu/data/mapper/toDomain/GptResponseDtoMapper.kt (2)
4-4: 변경사항 승인
SpeciesTypeimport 추가가 적절합니다.
14-15: 승인 — SpeciesType.displayName 확인됨app/src/main/java/com/example/findu/domain/model/breed/SpeciesType.kt에 val displayName이 정의되어 있고 companion의 fromString도 이를 사용하므로 하드코딩 제거로 타입 안전성 향상된 것 확인됩니다.
app/src/main/java/com/example/findu/presentation/util/extension/CharExt.kt (1)
3-7: 명시적 import 전환 좋습니다.실사용 심볼만 가져와 가독성과 정적 분석에 유리합니다. 현재 파일 내 사용처와 일치합니다.
app/src/main/res/layout/dialog_home_report_detail.xml (1)
6-6: rd15 배경 전환 LGTM일관성 있는 라운드 반경(15dp) 적용 좋습니다. 다크 모드 지원 여부만 확인 부탁드립니다(배경 드로어블이 흰색 고정이면 야간 테마에서 눈부심 가능).
app/src/main/res/layout/dialog_my_delete_history.xml (1)
6-6: rd15 배경 전환 LGTM시각적 일관성 OK. 야간 테마에서 배경 대비만 한 번 확인해 주세요.
app/src/main/res/layout/dialog_report_location.xml (1)
6-6: rd15 배경 전환 LGTM지도 카드(반경 20dp)와의 조합도 자연스럽습니다. 다만 배경이 흰색 고정이면 다크 모드에서 부자연스러울 수 있어요.
app/src/main/res/layout/dialog_home_report.xml (1)
6-6: rd15 배경 전환 LGTM동일 패턴의 다른 다이얼로그들과 일관성 확보되었습니다.
app/src/main/res/layout/dialog_my_withdrawal.xml (1)
18-18: rd15 배경 전환 LGTMoutlineProvider=background와 elevation(8dp) 조합에서 그림자 크롭 이슈 없는지 실제 기기에서만 체크 부탁드립니다.
app/src/main/res/layout/dialog_report_navigation.xml (1)
18-18: rd15 배경 전환 LGTM내부 컨테이너만 교체되어 터치 타깃/제약에는 영향 없습니다.
app/src/main/res/drawable/bg_report_dialog_rd15.xml (1)
5-5: 15dp 코너 반경을 dimen으로 분리하고 다크모드 drawable-night 추가
@dimen/dialog_corner_radius로 추출하고,drawable-night/bg_report_dialog_rd15.xml에 다크모드 색상(예:@color/black)을 설정해 주세요.bg_report_dialog_rd12참조는 더 이상 존재하지 않음을 확인했습니다.app/src/main/res/layout/dialog_report_finished.xml (3)
6-6: 배경 rd15 전환 좋아요다른 다이얼로그들과 톤 일치. 별도 이슈 없어 보입니다.
12-12: Top 마진 50dp 증가 — 작은 화면 컷오프 체크저해상도·짧은 높이(예: 18:9, 720p)에서 상단 아이콘/텍스트가 잘리지 않는지 한 번만 확인 부탁드립니다.
42-42: 고정 폭 248dp + 좌우 26dp — 가로 오버 가능성 점검DialogWindow 폭 계산에 따라 일부 디바이스에서 좌우 패딩 합산으로 공간이 빡빡할 수 있어요. 실제 소형 기기에서 가로 스크롤/컷 현상 없는지 확인 부탁.
app/src/main/res/layout/dialog_logout.xml (1)
19-19: 배경 rd15 업데이트 문제없음outlineProvider="background"와 엘리베이션 조합도 그대로라 그림자/클리핑 이슈 없어 보입니다.
app/src/main/java/com/example/findu/presentation/ui/report/component/ReportLocationComponent.kt (1)
103-113: isStopGesturesEnabled는 유효한 MapUiSettings 속성입니다
해당 속성은 Naver Maps Compose API에 공식 지원되는 토글 옵션으로, 오타가 아닙니다. 원 리뷰 코멘트는 잘못된 지적이므로 그대로 두시면 됩니다.Likely an incorrect or invalid review comment.
app/src/main/java/com/example/findu/data/mapper/torequest/AiDetectionRequestDtoMapper.kt (2)
6-6: 매퍼 동기화 LGTMDTO 변경 사항과 일관성 있게 매핑되어 있습니다.
6-6: Base64 포맷(줄바꿈/프리픽스/URL-safe) — 백엔드와 합의 필요
- 현재 구현: app/src/main/java/com/example/findu/presentation/util/UriUtil.kt — Base64.encodeToString(byteArray, Base64.NO_WRAP) 사용(라인 32, NO_WRAP → 줄바꿈 없음, android.util.Base64, 표준 Base64).
- 매퍼: app/src/main/java/com/example/findu/data/mapper/torequest/AiDetectionRequestDtoMapper.kt — base64Image = this (라인 6, 프리픽스 포함 여부 불명).
- 검색 결과: URL-safe API(Base64.getUrlEncoder/.base64Url) 미발견; 데이터 URI 프리픽스('data:image/...;base64,') 포함 여부 확인 불가.
요청(백엔드에 합의할 항목)
- NO_WRAP(줄바꿈 없음)으로 전송해도 되는가?
- 'data:image/;base64,' 프리픽스 포함해야 하는가?
- 표준 Base64 vs URL-safe(Base64Url) 중 어느 것을 사용할 것인가?
app/src/main/java/com/example/findu/presentation/ui/report/component/missing/ReportGenderComponent.kt (1)
112-112: 도메인 모델 변경에 맞춘 올바른 업데이트
Genderenum의value프로퍼티가displayName으로 변경된 것에 맞춰 UI 레이블을 적절히 업데이트했습니다. 변경사항이 일관성 있게 적용되었네요!app/src/main/java/com/example/findu/domain/model/report/Gender.kt (1)
3-4: 승인 — displayName 변경은 적절합니다. 전체 참조만 재확인하세요.value → displayName로 바꾼 덕분에 UI 표시 의도가 명확해졌습니다. 레포에 이전
.value참조(gender.value 등)가 남아있지 않은지 루트에서 아래 명령으로 확인하세요:# ripgrep (권장) rg -n --hidden -S '\.value\b' --glob '!**/build/**' --glob '!**/node_modules/**' -g '**/*.kt' -C2 rg -n --hidden -S 'gender\.value\b' --glob '!**/build/**' --glob '!**/node_modules/**' -g '**/*.kt' -C2 rg -n --hidden -S '\.displayName\b' --glob '!**/build/**' --glob '!**/node_modules/**' -g '**/*.kt' -C2 # ripgrep가 없거나 문제가 생기면 grep 대체명령 grep -RIn --exclude-dir=build --exclude-dir=node_modules --exclude-dir=.git -n -E -C2 "\.value\b" .app/src/main/java/com/example/findu/presentation/util/UriUtil.kt (3)
19-19: 메소드 시그니처 변경 승인nullable에서 non-null로 변경한 것은 좋은 개선입니다. 호출하는 쪽에서 null 체크를 하지 않아도 되니까요.
36-36: 일관된 에러 처리null 대신 빈 문자열("")을 반환하도록 변경한 것이 좋네요. inputStream이 null인 경우와 예외 발생 시 모두 동일하게 처리하니 일관성이 있습니다.
32-33: BASE64_PREFIX 제거 확인 — 호출부 검증 완료uriToBase64는 접두사 없이 순수 Base64 문자열을 반환하며, WitnessReportViewModel → postAiDetectionUseCase → toAiDetectionRequest → AiDetectionRequestDto.base64Image로 그대로 전달되므로 코드베이스 내에서는 접두사 불필요합니다.
UriUtil.kt에 남아있는 BASE64_PREFIX 상수는 사용처가 없으니 삭제 권장합니다.app/src/main/java/com/example/findu/presentation/ui/report/component/animalinfo/ReportSpeciesComponent.kt (1)
127-127: 표시 문자열 교체 OK
species.displayName사용으로 UI 일관성 확보된 점 좋습니다.app/src/main/java/com/example/findu/presentation/ui/report/component/witness/WitnessAnimalInfoComponent.kt (1)
60-60: 표시 문자열 교체 OK목격 신고 UI에서도
displayName으로 통일된 점 👍app/src/main/java/com/example/findu/domain/model/breed/SpeciesType.kt (1)
11-13: fromString 안전화 — 매칭 실패 시 ETC로 폴백 & Kotlin 버전 확인파일: app/src/main/java/com/example/findu/domain/model/breed/SpeciesType.kt (lines 11-13)
- fun fromString(value: String): SpeciesType = SpeciesType.entries.first { it.displayName == value } + fun fromString(value: String): SpeciesType = + SpeciesType.entries.firstOrNull { it.displayName == value.trim() } ?: ETC
- 주의:
entries프로퍼티는 Kotlin 1.9+ 전제입니다. 빌드 툴체인이 1.9 미만이면entries→values()로 교체하세요.app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/ReportRemoteDataSourceImpl.kt (1)
16-16: 확인 완료 — DTO 반환형 반영됨Repository/UseCase/Mapper에서 DTO→도메인 변환이 적용되어 있습니다: ReportRepositoryImpl.uploadImages(...)가 reportRemoteDataSource.uploadImages(...).handleBaseResponse().getOrThrow().toList()로 처리되며 ImageUploadResponseDto.toList()(ReportResponseDtoMapper.kt)가 urls를 반환합니다.
app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt (1)
12-19: v2 중복 경로 검증 필요Constraints.kt의 Naver 상수(REVERSE_GEOCODE, GEOCODE)가 "…/v2/…"를 포함하고 있으며 NaverService에서 @get("/$REVERSE_GEOCODE") / @get("/$GEOCODE")로 사용됩니다. 프로젝트 어딘가에서 VERSION = "v2" 상수를 경로에 결합하거나 Retrofit base URL에 'v2'가 포함돼 있으면 요청 경로에 '/v2/.../v2/...' 중복이 발생합니다. 현재 제공된 검색 결과에서는 'VERSION' 정의/사용을 찾지 못했으니 VERSION 정의 위치(또는 base URL에 v2 포함 여부)를 확인하고, 중복이 확인되면 Naver 상수에서 v2를 제거하거나 버전 사용 방식을 통일하세요.
참조: app/src/main/java/com/example/findu/data/dataremote/util/Constraints.kt (lines 16–19), app/src/main/java/com/example/findu/data/dataremote/service/NaverService.kt (lines 13,22).
app/src/main/java/com/example/findu/domain/model/report/MissingReportData.kt (1)
7-17: missingDate를 String으로 두지 마세요 — 도메인에선 날짜 타입 유지 권장
- 도메인 모델에서 날짜를 String으로 저장하면 포맷·타임존 문제가 런타임까지 숨어듭니다. LocalDate 또는 Instant를 사용하고 DTO 변환층에서만 문자열로 직렬화/역직렬화하세요 (프로젝트가 kotlinx.datetime 사용 시 kotlinx.datetime.LocalDate, JVM 전용이면 java.time.LocalDate).
- 추가 확인: rfid가 선택 입력이면 String? 또는 기본값("") 적용 여부를 비즈니스 규칙에 따라 검토하세요.
위치: app/src/main/java/com/example/findu/domain/model/report/MissingReportData.kt (라인 7–17)
app/src/main/java/com/example/findu/presentation/ui/report/component/missing/MissingAnimalInfoComponent.kt (1)
61-61: displayName 프로퍼티 사용으로 일관성 개선SpeciesType의 기존 species 프로퍼티에서 displayName으로 변경한 것은 UI 표시명을 명확히 하는 좋은 변경사항입니다.
app/src/main/java/com/example/findu/di/ServiceModule.kt (1)
97-97: 네이버 Maps API 베이스 URL 업데이트 확인됨네이버 클라우드 플랫폼 Maps API의 올바른 베이스 URL인 maps.apigw.ntruss.com 사용으로 변경되었습니다. 이 변경은 Geocoding과 Reverse Geocoding 기능을 지원하기 위해 필요한 업데이트입니다.
app/src/main/java/com/example/findu/data/dataremote/model/response/report/ImageUploadResponseDto.kt (1)
6-10: 이미지 업로드 응답 구조 개선기존의 단순한 List 대신 구조화된 DTO를 통해 API 응답을 더 명확하게 정의한 것은 좋은 변경사항입니다. 향후 추가 필드 확장 시에도 유연하게 대응할 수 있습니다.
app/src/main/java/com/example/findu/domain/model/report/LatLngData.kt (1)
3-6: 위치 데이터 모델 간결하고 명확함위도와 경도를 담는 심플한 data class로 목적에 맞게 잘 설계되었습니다. Double 타입 사용도 좌표 정밀도를 위해 적절합니다.
app/src/main/java/com/example/findu/data/mapper/toDomain/report/ReportResponseDtoMapper.kt (1)
5-5: 간결한 확장 함수로 매핑 처리ImageUploadResponseDto에서 urls 리스트를 추출하는 확장 함수가 간결하고 목적이 명확합니다.
app/src/main/java/com/example/findu/domain/repository/report/ReportRepository.kt (1)
26-26: 주소를 좌표로 변환하는 새 API 추가Geocoding 기능을 위한 getLatLng 메소드가 적절히 추가되었습니다. getAddress와 대칭적인 기능을 제공하여 양방향 변환이 가능합니다.
app/src/main/java/com/example/findu/data/dataremote/datasource/ReportRemoteDataSource.kt (2)
7-7: 새로운 응답 모델 import 추가 확인새로운
ImageUploadResponseDto모델을 import 하셨네요. 이 변경사항은 아래에서 다룰 메서드 시그니처 변경과 일치합니다.
11-11: 이미지 업로드 응답 구조 변경 — DTO로 마이그레이션 확인ImageUploadResponseDto(urls: List)와 ImageUploadResponseDto.toList() 확장함수가 있어 기존 List 사용처와 호환됩니다. (위치: com.example.findu.data.dataremote.model.response.report.ImageUploadResponseDto, com.example.findu.data.mapper.todomain.report.ImageUploadResponseDto.toList)
변경은 승인하나 runCatching/Result 기반 에러 핸들링이 호출부에서 적절히 처리되는지 확인.app/src/main/java/com/example/findu/domain/model/report/WitnessReportData.kt (4)
3-3: SpeciesType import 추가새로운
SpeciesTypeenum 클래스를 import 하셨네요. 이는 아래 필드에서 사용되는 타입입니다.
7-8: 품종 데이터 구조 개선
breedId: Int를 제거하고species: SpeciesType과breed: String으로 변경한 것은 좋은 개선입니다. 더 명확한 타입 안전성과 가독성을 제공합니다.
11-11: 랜드마크 필드 추가
landmark: String필드가 추가되었습니다. 이는 위치 정보를 보다 구체적으로 관리할 수 있게 해주는 좋은 추가입니다.
13-13: foundDate 타입 변경(Instant → String) — 날짜 포맷·검증 확인 필요
확인된 위치:
- app/src/main/java/com/example/findu/domain/model/report/WitnessReportData.kt (foundDate: String)
- app/src/main/java/com/example/findu/data/dataremote/model/request/ReportRequestDto.kt (@SerialName("foundDate") val foundDate: String)
- app/src/main/java/com/example/findu/data/mapper/torequest/ReportRequestDtoMapper.kt (foundDate = this.foundDate)
- app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt (foundDate = _uiState.value.witnessDate.toDateString())
- 참고: app/src/main/java/com/example/findu/presentation/ui/report/component/ReportDateComponent.kt 에서 DateTimeFormatter로 UI 포맷("yyyy년 M월 d일 (E)") 사용 중
확인 사항:
- 서버(API)가 기대하는 날짜 문자열 포맷이 무엇인지(예: ISO‑8601 등) 확인.
- toDateString()의 출력 포맷이 API 요구사항과 일치하는지 확인.
- 서버 전송 전/후에 날짜 포맷 검증이나 파싱(또는 DTO → Instant 변환)이 명확히 존재하는지 확인.
권장 조치: API가 문자열 포맷을 요구하면 toDateString()에 고정된 표준 포맷 적용 및 매퍼/DTO 레이어에서 포맷 검증 추가. API가 타임스탬프/Instant를 기대하면 Request DTO 또는 매퍼에서 kotlinx.datetime.Instant 사용으로 변경.
app/src/main/java/com/example/findu/domain/usecase/report/GetLatLngUseCase.kt (1)
1-12: 깔끔한 UseCase 구현주소를 위경도 좌표로 변환하는 UseCase 구현이 깔끔합니다. Result 타입을 사용한 명시적 에러 핸들링로 안전성도 보장되고 있네요. 의존성 주입과 operator fun 사용도 적절합니다.
app/src/main/java/com/example/findu/data/repositoryimpl/ReportRepositoryImpl.kt (6)
13-13: 새로운 매퍼 import
toList매퍼를 import 하셨네요. 이는ImageUploadResponseDto를List<String>으로 변환하는 용도로 보입니다.
18-18: LatLngData import 추가새로운 도메인 모델
LatLngData가 추가되었습니다. 위경도 정보를 담는 도메인 모델로 보입니다.
28-28: 생성자 파라미터 포맷팅trailing comma 추가는 일관된 코드 스타일을 위한 좋은 변경입니다.
34-34: 파라미터 포맷팅 일관성
encodeString파라미터에도 trailing comma가 추가되어 일관성이 유지되었습니다.
50-50: 이미지 업로드 응답 처리 변경
.toList()가 추가되어ImageUploadResponseDto를List<String>으로 변환하고 있습니다. 이는 도메인 레이어에서 기대하는 타입과 맞추기 위한 적절한 처리입니다.
72-75: 새로운 getLatLng 메서드 구현주소를 위경도로 변환하는 새로운 메서드가 추가되었습니다. runCatching을 사용한 Result 반환으로 안전한 에러 핸들링을 제공하고 있어 좋습니다.
app/src/main/java/com/example/findu/data/dataremote/datasource/NaverRemoteDataSource.kt (3)
3-4: 새로운 응답 모델 import
GeocodeResponseDto와ReverseGeocodeResponseDto를 import 하여 지오코딩 API 응답 구조를 명확히 구분하고 있습니다.
9-9: 응답 타입 명확화
getAddress메서드의 반환 타입이ReverseGeocodeResponseDto로 명확해졌습니다. 역지오코딩 응답임을 타입으로 명시하여 가독성이 향상되었습니다.
11-13: 새로운 지오코딩 메서드 추가주소를 위경도로 변환하는
getLatLng메서드가 추가되었습니다. 네이밍이 직관적이고 반환 타입도 명확합니다.app/src/main/java/com/example/findu/data/dataremote/service/ReportService.kt (3)
7-7: 새로운 응답 DTO import
ImageUploadResponseDtoimport가 추가되어 새로운 응답 구조를 지원합니다.
18-18: API 엔드포인트 경로 변경엔드포인트가
/api/v2/reports/images에서/api/v2/images/upload로 변경되었습니다. 더 RESTful한 구조로 개선된 것 같습니다.
21-21: 응답 타입 구조화반환 타입이
BaseResponse<ImageUploadResponseDto>로 변경되어 응답 구조가 더 명확해졌습니다. 이는 백엔드 API 변경에 맞는 적절한 업데이트입니다.app/src/main/java/com/example/findu/data/dataremote/datasourceimpl/NaverRemoteDataSourceImpl.kt (3)
4-5: 지오코딩 응답 모델 import 업데이트
GeocodeResponseDto와ReverseGeocodeResponseDtoimport로 지오코딩과 역지오코딩 응답을 구분해서 처리할 수 있게 되었습니다.
12-13: getAddress 메서드 반환 타입 업데이트반환 타입이
ReverseGeocodeResponseDto로 명확해져서 역지오코딩 응답임을 타입으로 표현하고 있습니다.
15-16: 새로운 getLatLng 메서드 구현주소를 위경도로 변환하는 메서드가 추가되었습니다. 인터페이스 구현이 깔끔하고 명확합니다.
app/src/main/java/com/example/findu/data/mapper/torequest/ReportRequestDtoMapper.kt (2)
10-21: MissingReportData 매핑 로직 구체화기존 플레이스홀더 값들을 실제 데이터 필드로 매핑하도록 개선되었습니다.
species.displayName과sex.displayName사용,furColors조인 로직 등이 실제 데이터를 활용하도록 잘 구현되었습니다.
25-33: WitnessReportData 매핑: furColors 조인 방식('&') 확인 필요목격 신고에서 furColors를 '&'로 조인한 것이 실종 신고(쉼표+공백)과 다릅니다. 백엔드 API/DTO 요구사항인지 확인 필요.
파일: app/src/main/java/com/example/findu/data/mapper/torequest/ReportRequestDtoMapper.kt (라인 25–33)
- API 스펙과 일치하면 승인.
- 불일치 시 매퍼에서 형식 통일(또는 서버 계약 조정) 필요.
자동 검증 시도 중 ripgrep이 kt 타입을 인식하지 못해 코드베이스에서 확증하지 못했습니다. 로컬에서 rg 또는 IDE로 furColor/ furColors 검색해 확인 필요.
app/src/main/java/com/example/findu/di/UseCaseModule.kt (3)
69-71: 새로운 UseCase 프로바이더 추가 확인
GetDetailSearchUseCase가 올바르게 DI 모듈에 추가되었습니다. 의존성 주입이 적절히 설정되었네요.
116-120: GetLatLngUseCase 프로바이더 추가 확인역지오코딩을 위한
GetLatLngUseCase가 적절히 추가되었습니다.
249-253: AI 품종 인식 UseCase 추가 확인
PostAiDetectionUseCase가 올바르게 추가되어 AI 품종 인식 기능을 지원합니다.app/src/main/java/com/example/findu/presentation/ui/report/screen/MissingReportScreen.kt (3)
78-92: 카메라 이동 이벤트 디바운싱 구현 확인카메라 이동 종료 시 2초 간격으로 디바운싱하여 서버 호출을 최적화한 구현이 좋습니다.
164-166: 로딩 다이얼로그 표시 로직 확인
LoadState.Loading상태일 때 적절히 로딩 인디케이터를 표시합니다.
191-192: 이미지 컴포넌트 콜백 파라미터 추가이미지 삭제 및 추가 기능을 위한 콜백에 인덱스가 추가되어 각 이미지를 구별할 수 있게 되었네요.
app/src/main/java/com/example/findu/presentation/ui/report/screen/WitnessReportScreen.kt (2)
76-90: 카메라 디바운싱 로직 확인MissingReportScreen과 동일한 패턴으로 카메라 이동 이벤트를 처리합니다. 일관성 있는 구현이 좋습니다.
190-192: AI 품종 인식 버튼 추가목격 신고에 AI 품종 인식 기능이 추가되어 사용자 편의성이 향상되었습니다.
app/src/main/java/com/example/findu/data/dataremote/service/NaverService.kt (2)
22-28: 새로운 지오코딩 엔드포인트 추가 확인주소를 위경도로 변환하는
getLatLng엔드포인트가 적절히 추가되었습니다.
13-20: 역지오코딩 엔드포인트 리팩토링하드코딩된 경로를
ApiConstraints의 상수로 변경하고 반환 타입을 명확히 했네요. 좋은 리팩토링입니다.app/src/main/java/com/example/findu/presentation/ui/common/LoadingIndicator.kt (2)
12-23: 로딩 인디케이터 구현 확인테마 색상을 활용한 일관된 디자인의 로딩 인디케이터입니다.
25-37: 다이얼로그 래퍼 구현 적절함로딩 인디케이터를 다이얼로그로 감싸서 전체 화면을 블로킹하는 UX를 제공합니다.
app/src/main/java/com/example/findu/presentation/ui/report/component/ReportImageComponent.kt (3)
229-251: 이미지 삭제 버튼 UI 구현 확인X 버튼을 통한 이미지 삭제 기능이 직관적으로 구현되었습니다. 원형 테두리와 배경색으로 시각적 계층이 명확합니다.
53-55: 콜백 시그니처 변경 확인페이지 인덱스와 이미지 URI를 전달하도록 콜백 시그니처가 적절히 변경되었습니다.
109-111: AI 품종 인식 버튼 로직현재 선택된 이미지에 대해서만 AI 인식이 가능하도록 적절히 구현되었습니다.
app/src/main/java/com/example/findu/presentation/ui/report/screen/ReportAnimalInfoScreen.kt (1)
151-154: clearFocus 중복 호출 가능성 — ViewModel 확인 필요ReportAnimalInfoScreen의 onSpeciesClick 콜백에서 clearFocus()를 호출하고 있습니다. WitnessReportViewModel.kt / MissingReportViewModel.kt의 OnSpeciesClick(또는 관련 이벤트) 처리에 focus 관련 로직이 있는지 확인해 중복 호출을 방지하세요.
위치: app/src/main/java/com/example/findu/presentation/ui/report/screen/ReportAnimalInfoScreen.kt (lines 151–154). 확인 대상: app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt, app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/MissingReportViewModel.kt.
| @SerialName("base64Image") | ||
| val base64Image: String |
There was a problem hiding this comment.
🧩 Analysis chain
서버 컨트랙트 변경 확인( imageUrl → base64Image )
필드/키 변경 자체는 문제 없어 보입니다. 다만 백엔드가 정확히 "base64Image" (케이스 포함) 키를 기대하는지, 그리고 레거시 "imageUrl" 참조가 남아있지 않은지 최종 확인 부탁드립니다. Swagger/OpenAPI 스키마도 함께 업데이트됐는지 체크 요청드려요.
다음 스크립트로 잔존 참조를 빠르게 점검할 수 있어요:
🏁 Script executed:
#!/bin/bash
# 남아있는 imageUrl 참조 검색 (build 아웃 제외)
rg -n -C2 -S '\bimageUrl\b' -g '!**/build/**'
# JSON 키로 남아있는 경우
rg -n -C2 -S '"imageUrl"\s*:' -g '!**/build/**'Length of output: 297
rg가 .gitignore를 존중해 build 필터로 모든 파일을 건너뛴 것 같습니다. 필터 없이 루트에서 다시 검색해보세요:
🏁 Script executed:
#!/bin/bash
# 모든 파일에서 imageUrl 참조 재검색
rg -n '\bimageUrl\b' .
# JSON 키로 남아있는 경우 재검색
rg -n '"imageUrl"\s*:' .Length of output: 2554
서버 컨트랙트 변경 확인( imageUrl → base64Image ) — 레거시 imageUrl 참조 다수 발견
요약: 요청 필드가 base64Image로 변경된 것으로 보이나 코드 곳곳에 여전히 imageUrl 참조가 남아 있어 수정 필요.
영향 파일(검색 결과):
- app/src/main/java/com/example/findu/domain/repository/BreedRepository.kt
- app/src/main/java/com/example/findu/domain/usecase/PostAiDetectionUseCase.kt
- app/src/main/java/com/example/findu/domain/model/search/DetailProtectData.kt
- app/src/main/java/com/example/findu/presentation/model/HomeRv.kt
- app/src/main/java/com/example/findu/data/repositoryimpl/ReportRepositoryImpl.kt
- app/src/main/java/com/example/findu/data/mapper/todomain/DetailSearchResponseDtoMapper.kt
- app/src/main/java/com/example/findu/data/repositoryimpl/BreedRepositoryImpl.kt
- app/src/main/java/com/example/findu/data/dataremote/model/request/GptRequestDto.kt
- app/src/main/java/com/example/findu/data/dataremote/model/response/DetailProtectResponseDto.kt
- app/src/main/java/com/example/findu/presentation/ui/home/adapter/HomeRVAdapter.kt
- app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt
- app/src/main/java/com/example/findu/presentation/ui/search/model/DetailSearchRv.kt
- app/src/main/java/com/example/findu/presentation/ui/search/detail/SearchProtectingDetailFragment.kt
- app/src/main/java/com/example/findu/presentation/ui/search/adapter/SearchDetailVPAdapter.kt
조치: postAiDetection 관련 시그니처(리포지토리/유스케이스/impl)와 toAiDetectionRequest(또는 해당 매퍼)를 base64Image 기반으로 수정하고, Swagger/OpenAPI 스펙도 동기화한 뒤 재검증하세요.
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/data/dataremote/model/request/AiDetectionRequestDto.kt
around lines 8-9, the server contract field changed from imageUrl to base64Image
but many places still reference imageUrl; update all related signatures and
mappings to use base64Image: change repository/usecase interfaces and
implementations (BreedRepository, ReportRepositoryImpl, BreedRepositoryImpl,
PostAiDetectionUseCase and its impl) to accept/send base64Image, update any
mapper/toAiDetectionRequest functions (DetailSearchResponseDtoMapper and any
to-domain mappers) and DTOs (GptRequestDto, DetailProtectResponseDto) to map the
image content into base64Image, and update presentation
layers/adapters/viewmodels (HomeRVAdapter, HomeRv, WitnessReportViewModel,
DetailProtectData, DetailSearchRv, SearchProtectingDetailFragment,
SearchDetailVPAdapter) to supply base64Image instead of imageUrl; also
synchronize the Swagger/OpenAPI spec, run compilation and tests, and verify no
residual imageUrl references remain.
| companion object { | ||
| fun fromString(value: String): FurColorType = FurColorType.entries.first { it.color == value } | ||
| } |
There was a problem hiding this comment.
fromString: 매칭 실패 시 NPE 가능 — 안전한 기본값으로 처리 제안
first { ... }는 매칭 실패 시 예외가 발생합니다. 사용자 입력/서버 문자열 불일치에 대비해 안전하게 처리하는 편이 좋아요.
- fun fromString(value: String): FurColorType = FurColorType.entries.first { it.color == value }
+ fun fromString(value: String): FurColorType =
+ FurColorType.entries.firstOrNull { it.color == value.trim() } ?: OTHERCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/src/main/java/com/example/findu/domain/model/report/FurColorType.kt
around lines 16 to 18, fromString currently uses first { it.color == value }
which throws if no match; change it to use firstOrNull { ... } and return a safe
default instead of risking an NPE — e.g., normalize the input
(trim()/toLowerCase()) for comparison, use firstOrNull and return
FurColorType.UNKNOWN (add an UNKNOWN/OTHER enum constant if none exists) or fall
back to a sensible default like entries.first() so the method never throws.
| onFailure = { error -> | ||
| _uiEffect.send( | ||
| MissingReportUiEffect.ShowToast( | ||
| message = error.message ?: "목격 신고 등록에 실패했습니다.", |
There was a problem hiding this comment.
에러 메시지가 잘못되었습니다
에러 메시지가 "목격 신고 등록에 실패했습니다"로 되어있는데, 이것은 MissingReportViewModel이므로 "실종 신고 등록에 실패했습니다"가 맞습니다.
- message = error.message ?: "목격 신고 등록에 실패했습니다.",
+ message = error.message ?: "실종 신고 등록에 실패했습니다.",📝 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.
| message = error.message ?: "목격 신고 등록에 실패했습니다.", | |
| message = error.message ?: "실종 신고 등록에 실패했습니다.", |
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/MissingReportViewModel.kt
around line 226, the error message text incorrectly says "목격 신고 등록에 실패했습니다." but
this ViewModel handles missing (실종) reports; change the string to "실종 신고 등록에
실패했습니다." so the user-visible error matches the missing-report context.
| val witnessReportData = WitnessReportData( | ||
| imageUrls = imageUrls, | ||
| species = _uiState.value.speciesType, | ||
| breed = _uiState.value.breed!!.name, |
There was a problem hiding this comment.
Null 안전성 문제
_uiState.value.breed!!.name에서 강제 언래핑을 사용하고 있습니다. breed가 null일 경우 크래시가 발생할 수 있습니다.
- breed = _uiState.value.breed!!.name,
+ breed = _uiState.value.breed?.name ?: "",또는 breed가 null이 아님을 보장하는 검증 로직을 추가해야 합니다.
📝 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.
| breed = _uiState.value.breed!!.name, | |
| breed = _uiState.value.breed?.name ?: "", |
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt
around line 206, the code uses a forced unwrap `_uiState.value.breed!!.name`
which can crash if breed is null; replace the forced unwrap with a null-safe
handling: either validate/guarantee breed is non-null before this line (return
early or throw a clear error), or use a safe-access pattern (e.g.,
`_uiState.value.breed?.name ?: "<default>"` or wrap the block in a `let`/`?.let
{ }` to only access name when breed is non-null) so the app won’t crash when
breed is null.
|
|
||
| private fun detectionWithAI(uri: Uri) { | ||
| _uiState.update { it.copy(loadState = LoadState.Loading) } | ||
| val base64Image = uri.uriToBase64(context) |
There was a problem hiding this comment.
🧩 Analysis chain
Base64 변환 메모리 사용량 체크
대용량 이미지를 Base64로 변환할 때 메모리 사용량이 급증할 수 있습니다. 이미지 크기 제한이나 리사이징 로직이 필요할 수 있습니다.
🏁 Script executed:
#!/bin/bash
# uriToBase64 구현에서 이미지 리사이징 로직 확인
rg -A10 "fun.*uriToBase64" --type=kotlinLength of output: 1253
이미지 Base64 변환에서 리사이징/스트리밍 적용 필요 (OOM 위험).
app/src/main/java/com/example/findu/presentation/util/UriUtil.kt의 fun Uri.uriToBase64가 전체 비트맵을 decode → compress(quality=100) → toByteArray로 처리하고 있어 대용량 이미지에서 메모리 폭증(잠재적 OOM)이 발생합니다.
- 위치: app/src/main/java/com/example/findu/presentation/util/UriUtil.kt (fun Uri.uriToBase64) — 호출: app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt (val base64Image = uri.uriToBase64(context)).
- 권장 조치:
- BitmapFactory.Options(inJustDecodeBounds → 계산된 inSampleSize)로 다운샘플링/리사이징(예: 최대 1024px 수준) 적용.
- 가능하면 InputStream → Base64OutputStream 같은 스트리밍 인코딩으로 전체 byte[] 생성을 피함.
- compress 품질을 100에서 낮추거나(예: 80) 업로드 전 파일 크기 제한 검증 추가.
🤖 Prompt for AI Agents
In
app/src/main/java/com/example/findu/presentation/ui/report/viewmodel/WitnessReportViewModel.kt
around line 253, the call val base64Image = uri.uriToBase64(context) uses a
Uri.uriToBase64 implementation that decodes the full bitmap then compresses to a
byte[] causing OOM for large images; update Uri.uriToBase64 in
app/src/main/java/com/example/findu/presentation/util/UriUtil.kt to (1) read
image bounds with BitmapFactory.Options(inJustDecodeBounds=true), compute an
inSampleSize to downscale so the longer edge is capped (e.g., 1024px) and decode
with that sample size, (2) avoid creating a full byte[] by streaming the image
InputStream through a Base64OutputStream into a temporary file or stream, and
(3) reduce JPEG compress quality (e.g., 80) and/or enforce a max upload size
check before encoding.
| fun String.toDateString(): String = | ||
| if (this.isBlank()) { | ||
| Clock.System.now() | ||
| .toLocalDateTime(TimeZone.currentSystemDefault()) | ||
| .date.toString() | ||
| } else { | ||
| val dateParts = this.replace("년 ", "-") | ||
| .replace("월 ", "-") | ||
| .replace("일", "") | ||
| .split("-") | ||
| val year = dateParts[0].toInt() | ||
| val month = dateParts[1].toInt() | ||
| val day = dateParts[2].toInt() | ||
| LocalDateTime(year, month, day, 0, 0) | ||
| .date.toString() | ||
| } No newline at end of file |
There was a problem hiding this comment.
요일 포함 문자열에서 NFE 발생 가능 및 공백 의존 파싱 취약
예: "2023년 08월 15일 (화)" → split("-") 후 day = "15 (화)"가 되어 toInt()에서 예외. 또한 "년 "/"월 "처럼 공백 존재를 전제로 하여 입력 변형에 취약합니다. 안전 파싱과 폴백을 추가해 주세요.
적용 제안(diff):
-fun String.toDateString(): String =
- if (this.isBlank()) {
- Clock.System.now()
- .toLocalDateTime(TimeZone.currentSystemDefault())
- .date.toString()
- } else {
- val dateParts = this.replace("년 ", "-")
- .replace("월 ", "-")
- .replace("일", "")
- .split("-")
- val year = dateParts[0].toInt()
- val month = dateParts[1].toInt()
- val day = dateParts[2].toInt()
- LocalDateTime(year, month, day, 0, 0)
- .date.toString()
- }
+fun String.toDateString(): String =
+ if (isBlank()) {
+ Clock.System.now()
+ .toLocalDateTime(TimeZone.currentSystemDefault())
+ .date.toString()
+ } else {
+ val fallback = {
+ Clock.System.now()
+ .toLocalDateTime(TimeZone.currentSystemDefault())
+ .date.toString()
+ }
+ // 요일 "(화)" 등 제거 + 공백/0-패딩 변형 허용
+ val cleaned = this
+ .substringBefore(" (") // "2023년 08월 15일 (화)" → "2023년 08월 15일"
+ .replace("년", "-")
+ .replace("월", "-")
+ .replace("일", "")
+ .replace(" ", "")
+ .trim('-')
+
+ val parts = cleaned.split("-").filter { it.isNotBlank() }
+ val year = parts.getOrNull(0)?.toIntOrNull()
+ val month = parts.getOrNull(1)?.toIntOrNull()
+ val day = parts.getOrNull(2)?.toIntOrNull()
+
+ val parsed = if (year != null && month != null && day != null) {
+ runCatching {
+ LocalDateTime(year, month, day, 0, 0).date.toString()
+ }.getOrNull()
+ } else null
+
+ parsed ?: fallback()
+ }
Related issue 🛠
Work Description 📝
Screenshot 📸
missing_report_api_0921.mp4
witness_report_api_0921.mp4
Summary by CodeRabbit
신규 기능
개선 사항
스타일