Skip to content

Comments

[Feat] 신고 API 연동#90

Merged
ikseong00 merged 19 commits intodevelopfrom
feat-report-api
Oct 8, 2025
Merged

[Feat] 신고 API 연동#90
ikseong00 merged 19 commits intodevelopfrom
feat-report-api

Conversation

@ikseong00
Copy link
Collaborator

@ikseong00 ikseong00 commented Sep 21, 2025

Related issue 🛠

Work Description 📝

  • 신고 관련 뷰와 API 를 연동했습니다.
  • 품종 인식 API 연동
  • 네이버 지도 Geocode(다음 API 주소를 통해 지도 카메라 타겟 변환) , ReverseGeocode(지도 카메라 타겟 이동)

Screenshot 📸

missing_report_api_0921.mp4
witness_report_api_0921.mp4

Summary by CodeRabbit

  • 신규 기능

    • 주소로 좌표 조회 지원(지도 위치 지정 향상)
    • 로딩 인디케이터/다이얼로그 추가
    • 신고 이미지 추가/삭제 기능 강화 및 이미지 선택 다이얼로그 개선
  • 개선 사항

    • 품종 검색 디바운스 적용 및 표시 목록 분리
    • 종/성별 라벨 표기를 보기 쉬운 이름으로 통일
    • 신고 폼 확인 버튼 조건 완화(입력 편의성 향상)
    • 지도 카메라 이동 시 주소 자동 갱신(스로틀 처리)
    • 화면 전환 애니메이션 조정(글로벌 페이드)
    • 지도 영역 높이 상향(가시성 개선)
  • 스타일

    • 다이얼로그 모서리 15dp로 통일, 일부 버튼 글자색을 흰색으로 변경

- 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 메시지 출력
- 이미지 등록 개수 제한 로직 추가
@coderabbitai
Copy link

coderabbitai bot commented Sep 21, 2025

Walkthrough

Naver 지도 API 연동 구조를 개편하고(정/역지오코딩 추가), 신고 이미지 업로드 응답 스키마를 변경했습니다. 품종/성별/색상 도메인 모델을 정리하고 UI 컴포넌트 시그니처를 업데이트했으며, 목격/실종 신고 ViewModel과 화면에 검색·로딩·지도 카메라 연동을 추가했습니다. 다수 레이아웃 리소스의 배경 라운드를 교체했습니다.

Changes

Cohort / File(s) Summary
Naver 지오코딩 API 도입
.../datasource/NaverRemoteDataSource.kt, .../datasourceimpl/NaverRemoteDataSourceImpl.kt, .../service/NaverService.kt, .../model/response/report/ReverseGeocodeResponseDto.kt, .../util/Constraints.kt, .../di/ServiceModule.kt
역지오코딩 반환타입을 ReverseGeocodeResponseDto로 교체, 정지오코딩 getLatLng 추가(GeocodeResponseDto). 엔드포인트 상수(GEOCODE/REVERSE_GEOCODE)와 Naver baseUrl 변경. DTO 교체/추가.
지오코딩 도메인/레포/유즈케이스
.../domain/model/report/LatLngData.kt, .../data/mapper/toDomain/report/NaverResponseDtoMapper.kt, .../domain/repository/report/ReportRepository.kt, .../data/repositoryimpl/ReportRepositoryImpl.kt, .../domain/usecase/report/GetLatLngUseCase.kt, .../di/UseCaseModule.kt
LatLngData 신설, Geocode/ReverseGeocode 매퍼 추가, 레포에 getLatLng 추가 및 구현, 유즈케이스/DI 제공자 추가.
신고 이미지 업로드 스키마 변경
.../datasource/ReportRemoteDataSource.kt, .../datasourceimpl/ReportRemoteDataSourceImpl.kt, .../service/ReportService.kt, .../model/response/report/ImageUploadResponseDto.kt, .../data/mapper/toDomain/report/ReportResponseDtoMapper.kt
업로드 응답을 BaseResponse로 변경하고 urls 추출 매퍼 추가. 엔드포인트 경로 변경(/api/v2/images/upload).
AI 탐지 요청/유틸
.../model/request/AiDetectionRequestDto.kt, .../mapper/torequest/AiDetectionRequestDtoMapper.kt, .../presentation/util/UriUtil.kt
요청 필드 imageUrl→base64Image로 변경. Uri→Base64가 non-null 반환(빈 문자열 처리) 및 prefix 제거.
품종/성별/색상 도메인 정리
.../domain/model/breed/BreedData.kt, .../domain/model/breed/SpeciesType.kt, .../domain/model/report/Gender.kt, .../domain/model/report/FurColorType.kt, .../data/mapper/toDomain/GptResponseDtoMapper.kt
Breed 계층에 id 도입 및 기본값, SpeciesType/Gender에 displayName로 필드 교체, fromString 유틸 추가, GPT 매핑에서 displayName 기준 비교로 변경.
신고 요청 매핑 업데이트
.../mapper/torequest/ReportRequestDtoMapper.kt, .../domain/model/report/MissingReportData.kt, .../domain/model/report/WitnessReportData.kt
Missing/Witness 데이터 모델과 DTO 매핑을 실제 입력 필드 기반으로 수정(날짜 String, 랜드마크, 종/품종 문자열 등).
신고 화면/VM: 로딩·검색·지도 연동
.../presentation/ui/report/viewmodel/MissingReportViewModel.kt, .../presentation/ui/report/viewmodel/WitnessReportViewModel.kt, .../presentation/ui/report/screen/MissingReportScreen.kt, .../presentation/ui/report/screen/WitnessReportScreen.kt
Breed 데이터 비동기 로딩, 카메라 idle 시 지오코딩 이벤트 디바운스, 로딩 다이얼로그 표시, 이미지 추가/삭제/AI 탐지 흐름과 주소↔좌표 변환 처리 추가.
리포트 입력 컴포넌트 변경
.../presentation/ui/report/component/ReportImageComponent.kt, .../presentation/ui/report/component/animalinfo/ReportBreedComponent.kt, .../presentation/ui/report/component/animalinfo/ReportSpeciesComponent.kt, .../presentation/ui/report/component/missing/ReportGenderComponent.kt, .../presentation/ui/report/component/witness/WitnessAnimalInfoComponent.kt, .../presentation/ui/report/component/missing/MissingAnimalInfoComponent.kt, .../presentation/ui/report/component/ReportLocationComponent.kt
콜백 시그니처 정교화(페이지 인덱스/Uri 전달, 탐지 네이밍 변경), 품종 검색 트리거 및 표시 리스트 분리, displayName 표시로 변경, 맵 영역 높이 및 마커 컴포저블 제거.
네비게이션 트랜지션
.../presentation/ui/report/navigation/MissingReportNavHost.kt, .../presentation/ui/report/navigation/WitnessReportNavHost.kt
NavHost 레벨 페이드 인/아웃(0ms) 추가, 일부 화면 exitTransition 제거/조정.
로딩 UI
.../presentation/ui/common/LoadingIndicator.kt
로딩 인디케이터/다이얼로그 컴포저블 및 프리뷰 추가.
유틸 확장
.../presentation/util/extension/CharExt.kt
String.toDateString() 추가(“YYYY년 MM월 DD일” → “YYYY-MM-DD”, 빈 값은 오늘).
리소스 스타일 조정
app/src/main/res/drawable/bg_report_dialog_rd15.xml, app/src/main/res/layout/dialog_*.xml, .../fragment_report_camera.xml
다이얼로그 배경 12dp→15dp로 교체, 일부 마진/텍스트색 조정, 버튼 텍스트색 흰색 지정.

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
Loading
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: 성공/실패 상태 반영
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

  • [Feat] 로그인 API 구현 #77: ApiConstraints 추가 작업과 동일 파일 편집이 겹치며 경로 상수 도입 맥락이 연결됩니다.
  • [Feat] mypage UI #75: dialog_logout.xml 배경 리소스 교체가 동일 파일에서 충돌 가능성이 있습니다.
  • [Feat] 신고 API 구현 #87: 신고/품종/AI 탐지 파이프라인 확장과 DTO/유즈케이스 변경이 직접적으로 연속된 변경입니다.

Suggested reviewers

  • t1nm1ksun
  • nasohee
  • MinseoSONG

Poem

둥근 모서리, 지도가 속삭여
주소는 좌표로, 좌표는 주소로—
사진은 구름에, URL로 춤추고
품종은 이름을, 화면은 빛을 얻네 ✨
로딩 고리 빙글빙글, 이제 신고 출발! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description Check ⚠️ Warning PR 설명에는 관련 이슈(closed #89), 작업 요약(신고 UI 및 API 연동, 품종 인식, Geocode/ReverseGeocode)와 스크린샷 링크가 포함되어 있어 전반적 맥락은 잘 제공됩니다. 다만 저장소의 설명 템플릿에 명시된 "Uncompleted Tasks 😅" 체크리스트와 "To Reviewers 📢" 섹션이 비어 있거나 누락되어 있어 템플릿 요구사항을 완전히 충족하지 못합니다. 또한 리뷰어가 주의해야 할 브레이킹 체인지(예: 공개 시그니처 변경, 도메인 모델 변경)나 테스트 가이드는 별도로 적혀 있지 않습니다. 템플릿에 맞춰 "Uncompleted Tasks"에 남은 작업 또는 확인해야 할 항목을 체크박스로 추가하고 "To Reviewers"에 리뷰 시 주의할 점(브레이킹 변경 목록, 의존성 영향, 수동 검사/테스트 방법 등)을 간단히 명시해 주세요; 또한 API 계약(엔드포인트/응답 스펙) 변경이 있다면 해당 버전·예시 응답을 첨부하면 리뷰 속도가 빨라집니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.79% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes Check ❓ Inconclusive 대부분 변경은 신고 API·품종 인식·지오코드 연동 관련으로 보이나, PR 전역에 걸친 교차 영향 변경들이 다수 포함되어 있어 범위 판정이 완전히 명확하지 않습니다: 예를 들어 SpeciesType/Gender/FurColorType의 프로퍼티명/매핑 함수 추가, Breed 계층·기본값 변경, 여러 Composable의 공개 시그니처 변경(ReportBreedComponent, ReportImageComponent 등), 그리고 내비게이션 전환·리소스 스타일 수정 등은 신고 API 연동 외부의 다른 모듈에도 영향을 줄 수 있습니다. 따라서 "완전히 범위 내"인지 또는 다른 기능에 파급효과가 있는지는 추가 확인이 필요합니다. 머지 전 전체 빌드와 앱 전역 검색로 변경된 심볼 사용처를 검증하고(컴파일/런타임 에러 확인), 영향 범위가 넓은 공개 시그니처·도메인 모델 변경은 별도 PR로 분리하거나 변경 목록과 마이그레이션 가이드를 PR 본문/리뷰 코멘트에 명확히 기재해 주세요; 또한 주요 변경(특히 enum/모델명 변경)은 다른 팀·모듈과의 호환성을 검증해 주세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 "[Feat] 신고 API 연동"은 PR의 핵심 변경사항(신고 관련 API 및 연동)을 간결하게 요약하고 있어 깔끔하고 목적이 명확합니다; 불필요한 파일 목록·이모지·모호한 표현이 없어 히스토리 스캔 시 주요 변경을 빠르게 파악할 수 있습니다. 변경 범위가 넓지만 제목은 지나치게 일반적이지 않고 실제 작업과 일치합니다. 따라서 제목은 적절합니다.
Linked Issues Check ✅ Passed 링크된 이슈(#89)의 목표(품종 API, 목격 신고 API, 실종 신고 API 연동)에 대해 PR 변경 내역은 직접적인 구현 증거를 포함합니다: 품종 인식(PostAiDetectionUseCase/Api), 이미지 업로드 변경 및 ImageUploadResponseDto, Naver Geocode/ReverseGeocode DTO·서비스·usecase·repo·viewmodel 연동, 그리고 목격/실종 뷰모델의 보고 흐름 구현이 모두 확인됩니다. 코드 변경은 연관된 API 엔드포인트·DTO·usecase·viewmodel을 포괄적으로 연결하고 있어 이슈의 코딩 요구사항을 충족한 것으로 보입니다. 다만 런타임 검증(통합 테스트/백엔드 계약 확인)은 PR 본문에 명시되어 있지 않습니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-report-api

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ikseong00 ikseong00 marked this pull request as ready for review September 21, 2025 13:02
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.LocalDate import 추가 후 아래처럼 간결화하세요.

-        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.rememberCameraPositionState
app/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) 제거 제안

현재 상위 Breedid/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.debounce
app/src/main/java/com/example/findu/data/dataremote/model/response/report/ReverseGeocodeResponseDto.kt (2)

8-57: 파일명과 클래스명이 일치하지 않습니다

파일명은 ReverseGeocodeResponseDto.kt인데 파일 내에 GeocodeResponseDtoReverseGeocodeResponseDto 두 개의 클래스가 정의되어 있습니다. 코드 구조를 명확히 하기 위해 각 DTO를 별도 파일로 분리하는 것이 좋겠습니다.

-// ReverseGeocodeResponseDto.kt 파일에서
-@Serializable
-data class GeocodeResponseDto(
-    // ...
-)
-
-@Serializable
-data class ReverseGeocodeResponseDto(
-    // ...
-)
+// GeocodeResponseDto.kt 파일로 분리
+// ReverseGeocodeResponseDto.kt는 ReverseGeocodeResponseDto만 포함

19-19: 타입 이름 오타: AddresseAddress

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

📥 Commits

Reviewing files that changed from the base of the PR and between f6d1f0c and c061cc5.

📒 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: 변경사항 승인

SpeciesType import 추가가 적절합니다.


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 배경 전환 LGTM

outlineProvider=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: 매퍼 동기화 LGTM

DTO 변경 사항과 일관성 있게 매핑되어 있습니다.


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,') 포함 여부 확인 불가.

요청(백엔드에 합의할 항목)

  1. NO_WRAP(줄바꿈 없음)으로 전송해도 되는가?
  2. 'data:image/;base64,' 프리픽스 포함해야 하는가?
  3. 표준 Base64 vs URL-safe(Base64Url) 중 어느 것을 사용할 것인가?
app/src/main/java/com/example/findu/presentation/ui/report/component/missing/ReportGenderComponent.kt (1)

112-112: 도메인 모델 변경에 맞춘 올바른 업데이트

Gender enum의 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 미만이면 entriesvalues()로 교체하세요.
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 추가

새로운 SpeciesType enum 클래스를 import 하셨네요. 이는 아래 필드에서 사용되는 타입입니다.


7-8: 품종 데이터 구조 개선

breedId: Int를 제거하고 species: SpeciesTypebreed: 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)") 사용 중
  • 확인 사항:

    1. 서버(API)가 기대하는 날짜 문자열 포맷이 무엇인지(예: ISO‑8601 등) 확인.
    2. toDateString()의 출력 포맷이 API 요구사항과 일치하는지 확인.
    3. 서버 전송 전/후에 날짜 포맷 검증이나 파싱(또는 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 하셨네요. 이는 ImageUploadResponseDtoList<String>으로 변환하는 용도로 보입니다.


18-18: LatLngData import 추가

새로운 도메인 모델 LatLngData가 추가되었습니다. 위경도 정보를 담는 도메인 모델로 보입니다.


28-28: 생성자 파라미터 포맷팅

trailing comma 추가는 일관된 코드 스타일을 위한 좋은 변경입니다.


34-34: 파라미터 포맷팅 일관성

encodeString 파라미터에도 trailing comma가 추가되어 일관성이 유지되었습니다.


50-50: 이미지 업로드 응답 처리 변경

.toList()가 추가되어 ImageUploadResponseDtoList<String>으로 변환하고 있습니다. 이는 도메인 레이어에서 기대하는 타입과 맞추기 위한 적절한 처리입니다.


72-75: 새로운 getLatLng 메서드 구현

주소를 위경도로 변환하는 새로운 메서드가 추가되었습니다. runCatching을 사용한 Result 반환으로 안전한 에러 핸들링을 제공하고 있어 좋습니다.

app/src/main/java/com/example/findu/data/dataremote/datasource/NaverRemoteDataSource.kt (3)

3-4: 새로운 응답 모델 import

GeocodeResponseDtoReverseGeocodeResponseDto를 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

ImageUploadResponseDto import가 추가되어 새로운 응답 구조를 지원합니다.


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 업데이트

GeocodeResponseDtoReverseGeocodeResponseDto import로 지오코딩과 역지오코딩 응답을 구분해서 처리할 수 있게 되었습니다.


12-13: getAddress 메서드 반환 타입 업데이트

반환 타입이 ReverseGeocodeResponseDto로 명확해져서 역지오코딩 응답임을 타입으로 표현하고 있습니다.


15-16: 새로운 getLatLng 메서드 구현

주소를 위경도로 변환하는 메서드가 추가되었습니다. 인터페이스 구현이 깔끔하고 명확합니다.

app/src/main/java/com/example/findu/data/mapper/torequest/ReportRequestDtoMapper.kt (2)

10-21: MissingReportData 매핑 로직 구체화

기존 플레이스홀더 값들을 실제 데이터 필드로 매핑하도록 개선되었습니다. species.displayNamesex.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.

Comment on lines +8 to +9
@SerialName("base64Image")
val base64Image: String
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 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.

Comment on lines +16 to +18
companion object {
fun fromString(value: String): FurColorType = FurColorType.entries.first { it.color == value }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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() } ?: OTHER

Committable 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 ?: "목격 신고 등록에 실패했습니다.",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

에러 메시지가 잘못되었습니다

에러 메시지가 "목격 신고 등록에 실패했습니다"로 되어있는데, 이것은 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.

Suggested change
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,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Base64 변환 메모리 사용량 체크

대용량 이미지를 Base64로 변환할 때 메모리 사용량이 급증할 수 있습니다. 이미지 크기 제한이나 리사이징 로직이 필요할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# uriToBase64 구현에서 이미지 리사이징 로직 확인
rg -A10 "fun.*uriToBase64" --type=kotlin

Length 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)).
  • 권장 조치:
    1. BitmapFactory.Options(inJustDecodeBounds → 계산된 inSampleSize)로 다운샘플링/리사이징(예: 최대 1024px 수준) 적용.
    2. 가능하면 InputStream → Base64OutputStream 같은 스트리밍 인코딩으로 전체 byte[] 생성을 피함.
    3. 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.

Comment on lines +61 to +76
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

요일 포함 문자열에서 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()
+    }

@ikseong00 ikseong00 merged commit 69256ee into develop Oct 8, 2025
1 check passed
@ikseong00 ikseong00 deleted the feat-report-api branch October 8, 2025 08:36
@coderabbitai coderabbitai bot mentioned this pull request Oct 8, 2025
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 신고 API 연동

1 participant