Skip to content

[Feature/#139] 제보하기 기능을 구현합니다.#150

Merged
wjdrjs00 merged 48 commits intodevelopfrom
feature/#139-report
Nov 21, 2025
Merged

[Feature/#139] 제보하기 기능을 구현합니다.#150
wjdrjs00 merged 48 commits intodevelopfrom
feature/#139-report

Conversation

@wjdrjs00
Copy link
Copy Markdown
Member

@wjdrjs00 wjdrjs00 commented Nov 21, 2025

[ PR Content ]

Related issue

Screenshot 📸

  • N/A

Work Description

To Reviewers 📢

  • N/A

Summary by CodeRabbit

  • 새로운 기능

    • 신고(제보) 화면 추가 — 카테고리 선택, 제목/내용 입력, 제출 플로우
    • 이미지 첨부(최대 3장) 및 카메라/앨범 선택, 사진 미리보기/삭제
    • 이미지 업로드 및 신고 서버 제출 기능
  • 권한

    • 카메라 및 위치 권한 추가 및 안내/설정 이동 다이얼로그 제공
  • UI/디자인

    • 재사용 가능한 텍스트필드·경고 대화상자·플로팅 버튼 항목·다수 아이콘 추가
  • 기타

    • 파일 제공자 설정 및 위치/주소 자동 조회 지원

✏️ Tip: You can customize this high-level summary in your review settings.

- 펙토리 메서드에 파라미터를 추가하여 커스텀 가능하도록 변경
- 제보하기 화면의 UI 및 기본 로직을 구현합니다.
- 현재 위치를 기반으로 도로명 주소를 조회하는 UseCase를 추가합니다.
- 주소 관련 기능을 위한 AddressRepository 인터페이스를 정의합니다.
- PresignedUrl 업로드를 위한  ImageUploader 구현
- Qualifier 선언 모듈 변경 (app -> network)
[Feature/#141] 제보하기 화면 UI를 구현합니다.
[Feature/#143] 현재 위치 기반 주소 조회 기능을 구현합니다.
- 이미지 파일 목록을 받아 Presigned URL을 요청합니다.
- 발급받은 Presigned URL을 사용하여 S3에 이미지를 업로드합니다.
- 업로드 성공 시, 이미지의 key 목록을 반환합니다.
- ReportCategory enum class 정의
- 제보하기 API service를 추가합니다.
- 제보하기 관련 data, domain 레이어 모듈(DataSource, Repository, UseCase)을 추가합니다.
- 제보하기 관련 DI Module을 추가합니다.
[Feature/#145] 제보 제출 로직을 구현합니다..
- ReportState, ReportSideEffect, SubmitState를 model 패키지로 분리
- 제보 제출(submitReportWithImages) 시, 최소 1초의 딜레이 보장
- 제보 제목 입력 후 '완료'를 누르면 카테고리 선택 창이 나타나도록 수정
- 카테고리 선택 후 제보 내용 입력 필드에 자동으로 포커스가 가도록 수정
- 제보 내용 입력 후 '완료'를 누르면 키보드가 내려가도록 수정
[UI/#148] 제보하기 로딩 및 완료 화면을 구현합니다.
@wjdrjs00 wjdrjs00 requested a review from l5x5l November 21, 2025 11:19
@wjdrjs00 wjdrjs00 self-assigned this Nov 21, 2025
@wjdrjs00 wjdrjs00 added ✨ Feature 새로운 기능 구현 🧤 대현 labels Nov 21, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Nov 21, 2025

Walkthrough

제보 기능 전체를 추가했습니다: 권한 처리(카메라/위치), 이미지 캡처·변환·업로드, 좌표→주소 조회(Kakao API), 제보 제출 API 연동, 네비게이션(Report 라우트) 및 여러 UI 컴포넌트와 ViewModel/UseCase/Repository/DataSource/Service 바인딩을 포함합니다.

Changes

Cohort / File(s) 변경 요약
네비게이션 / 라우트
app/src/main/java/com/threegap/bitnagil/Route.kt, app/src/main/java/com/threegap/bitnagil/MainNavHost.kt, app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt
Report 라우트 추가 및 홈 FAB에서 제보 네비게이션 연결
AndroidManifest / FileProvider / 권한
app/src/main/AndroidManifest.xml, presentation/src/main/res/xml/file_paths.xml
CAMERA, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION 권한 추가, camera uses-feature 추가, FileProvider 등록 및 file_paths 리소스 추가
빌드 설정 / 키 관리
app/build.gradle.kts, .github/workflows/develop_branch.yml, gradle/libs.versions.toml, core/network/build.gradle.kts, data/build.gradle.kts, presentation/build.gradle.kts
KAKAO_REST_API_KEY 처리 추가(Gradle/env/CI), accompanist-permissions·play-services-location·coroutines-play 의존성 추가, javax.inject 의존성 추가
네트워크·DI (Kakao)
core/network/src/main/java/com/threegap/bitnagil/network/Qualifier.kt, app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt
@Kakao 한정자 추가, Kakao Interceptor/OkHttp/Retrofit 구성 및 KAKAO REST API 키 주입
DI 바인딩 확장
app/src/main/java/com/threegap/bitnagil/di/data/*
Address/File/Report 관련 DataSource/Repository/Service 바인딩 추가 (DataSourceModule, RepositoryModule, ServiceModule)
도메인: 주소 / 위치
domain/.../address/*
CurrentLocation/CurrentAddress 모델, AddressRepository 인터페이스, FetchCurrentAddressUseCase 추가
도메인: 파일 / 이미지
domain/.../file/*
ImageFile 모델(바이트 비교), FileRepository, UploadReportImagesUseCase 추가
도메인: 제보
domain/.../report/*
Report 모델, ReportCategory enum, ReportRepository, SubmitReportUseCase 추가
데이터: 주소 API / DTO
data/src/.../address/*
Coord2AddressResponse DTO, AddressService(Retrofit), LocationModule(FusedLocationProviderClient), AddressDataSource/Impl 추가
데이터: 파일 API / 업로더
data/src/.../file/*
FileInfoRequestDto, FileService(Retrofit), FileDataSource/Impl, ImageUploader(OkHttp PUT) 추가
데이터: 제보 API
data/src/.../report/*
ReportRequestDto, ReportService(Retrofit), ReportDataSource/Impl, ReportRepositoryImpl 추가
프레젠테이션: 권한 처리
presentation/src/main/java/com/threegap/bitnagil/presentation/common/premission/*
PermissionHandler 인터페이스 및 rememberPermissionHandler(Composable) 추가 (Accompanist 기반, 설정 이동 로직 포함)
프레젠테이션: 파일/카메라 유틸
presentation/src/main/java/com/threegap/bitnagil/presentation/common/file/ImageFileConverter.kt
URI→ImageFile 변환, 파일명 추출, Context.createCameraImageUri 확장 추가
프레젠테이션: 제보 화면·VM·상태
presentation/src/main/java/com/threegap/bitnagil/presentation/report/*
ReportScreenContainer, ReportViewModel(Orbit), ReportState, ReportSideEffect, SubmitState, ReportCategoryExtension 및 관련 UseCase 연계 추가
프레젠테이션: 제보 UI 컴포넌트
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/**
AddPhotoButton, PhotoItem, CurrentLocationInput, ReportCategorySelector, ReportField, ImageSourceBottomSheet, ReportCategoryBottomSheet, CompleteReportCard 등 다수 컴포저블 추가
프레젠테이션: 제출/완료 템플릿
presentation/src/.../component/template/*
SubmittingReportContent, CompleteReportContent 추가
디자인 시스템: 컴포넌트 변경
core/designsystem/src/main/java/.../BitnagilTextField.kt, BitnagilTextButton.kt, BitnagilFloatingButton.kt, BitnagilAlertDialog.kt
BitnagilTextField 추가, BitnagilTextButton.default 파라미터화, FloatingButton 레이아웃 패딩 변경, LogoutConfirmDialog → BitnagilAlertDialog로 리네임·파라미터화
디자인 시스템: 드로어블 리소스
core/designsystem/src/main/res/drawable/ic_*.xml (여러 파일 추가/삭제)
다수 아이콘 추가(ic_camera, ic_location 등), 기존 ic_report 삭제
설정 화면 변경
presentation/src/main/java/com/threegap/bitnagil/presentation/setting/SettingScreen.kt
LogoutConfirmDialog 사용처를 BitnagilAlertDialog로 마이그레이션

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as ReportScreen
    participant VM as ReportViewModel
    participant UC as UseCases
    participant Repo as Repository
    participant DS as DataSource
    participant API as RemoteService
    participant Storage as S3/Presigned

    User->>UI: 이미지 선택/카메라 실행
    UI->>VM: addImages(uris)
    VM-->>VM: state 업데이트

    User->>UI: 위치 조회 요청
    UI->>VM: fetchCurrentAddress()
    VM->>UC: FetchCurrentAddressUseCase.invoke()
    UC->>Repo: fetchCurrentLocation(), fetchCurrentAddress(lng,lat)
    Repo->>DS: LocationDataSource / AddressDataSource
    DS->>API: FusedLocationProviderClient / Kakao Address API
    API-->>DS: 위치/주소 응답
    DS-->>Repo: Result<String/Location>
    Repo-->>UC: Result<CurrentAddress>
    UC-->>VM: Result -> state 업데이트

    User->>UI: 제보 제출
    UI->>VM: submitReportWithImages()
    VM->>UC: UploadReportImagesUseCase (이미지 DTO 생성)
    UC->>Repo: uploadImages -> FileDataSource.fetchPresignedUrls()
    Repo->>API: FileService POST /presigned-urls
    API-->>Repo: Map<key,url>
    par 병렬 업로드
      VM->>Storage: PUT image -> presigned URL (ImageUploader)
      VM->>Storage: ... (동시)
    end
    Storage-->>VM: 200 OK
    VM->>UC: SubmitReportUseCase(reportDto)
    UC->>Repo: submitReport
    Repo->>API: ReportService POST /reports
    API-->>Repo: BaseResponse<Long>
    Repo-->>UC: Result<Long>
    UC-->>VM: Result -> submitState 업데이트
    VM->>UI: 완료 상태 표시 / navigate back
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

검토 시 추가 주의 항목:

  • ReportViewModel.kt: 권한 흐름, 코루틴 병렬 업로드, 오류/부분 실패 처리, 최소 대기 로직
  • ImageUploader.kt / FileRepositoryImpl.kt: presigned URL PUT 구현의 예외 메시지 및 응답 본문 처리
  • LocationDataSourceImpl.kt: FusedLocationProviderClient 사용과 권한(권한 체크 생략/주입) 안전성
  • NetworkModule.kt: Interceptor 체인과 @Kakao 한정자 충돌 가능성
  • rememberPermissionHandler.kt: Accompanist 상태 재진입/설정 이동 로직과 UI 동기화

Possibly related PRs

Suggested labels

📱 UI

Suggested reviewers

  • l5x5l

Poem

🐰 한 뼘의 용기로 셔터를 눌러
길 위의 이야길 한 장에 담았네
좌표와 사진, 말 한 줄 모아 올리면
작은 제보가 동네를 밝히리라 ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명이 관련 이슈(#139)를 명시하고 있으나, 제공된 템플릿의 필수 섹션이 대부분 누락되거나 미흡합니다. Work Description에 변경사항을 구체적으로 설명하고, To Reviewers에 검토 시 주의할 사항을 작성하세요. 스크린샷도 추가해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 1.64% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues check ❓ Inconclusive PR이 #139의 3가지 완료 작업(화면 UI, 로직, API 연동)을 구현했으나, 로딩/완료 UI는 미완료 상태입니다. 로딩/완료 UI 구현 여부를 명확히 하거나, 미완료 작업에 대한 설명을 PR 설명에 추가하세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 주요 변경사항인 '제보하기 기능'을 명확하게 요약하고 있으며, 간결하고 구체적입니다.
Out of Scope Changes check ✅ Passed 카메라 권한, 위치 권한, 파일 제공자 등 #139 범위 내 제보 기능에 필수적인 변경사항들이 포함되어 있습니다.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#139-report

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b602bee and da1655a.

📒 Files selected for processing (1)
  • .github/workflows/develop_branch.yml (1 hunks)
🔇 Additional comments (1)
.github/workflows/develop_branch.yml (1)

27-27: Kakao REST API 키 설정이 올바르게 추가됨을 확인했습니다.

기존의 KAKAO_NATIVE_APP_KEY 처리 패턴과 동일하게 KAKAO_REST_API_KEY가 환경변수로 정의되고 local.properties에 추가되고 있습니다. 제보 기능에 필요한 Kakao 좌표→주소 조회 API 연동을 위한 적절한 설정입니다.

다만, GitHub 저장소의 Secrets 설정에서 KAKAO_REST_API_KEY가 이미 등록되어 있는지 확인해주시기 바랍니다. 이 secret이 설정되지 않으면 CI 빌드 시 local.properties 생성이 의도한 대로 작동하지 않을 수 있습니다.

Also applies to: 35-35


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.

Copy link
Copy Markdown

@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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
data/src/main/java/com/threegap/bitnagil/data/address/model/response/Coord2AddressResponse.kt (1)

21-76: road_address null 응답 처리 누락으로 디코딩 예외 발생
카카오 coord2address API는 좌표에 도로명주소가 없으면 road_address 필드 자체를 null로 내려보냅니다.(devtalk.kakao.com) 현재 모델이 roadAddress: RoadAddress로 고정돼 있어 해당 케이스에서 JsonDecodingException이 발생하며, 확장 함수 toAddress()도 그 상황을 전혀 다루지 못합니다. roadAddress를 nullable로 바꾸고, 매핑 시 지번 주소로 안전하게 폴백하도록 수정해 주세요.

@@
-data class Document(
-    @SerialName("road_address")
-    val roadAddress: RoadAddress,
+data class Document(
+    @SerialName("road_address")
+    val roadAddress: RoadAddress?,
@@
-fun Coord2AddressResponse.toAddress(): String? {
-    return this.documents
-        .firstOrNull()
-        ?.roadAddress
-        ?.addressName
-}
+fun Coord2AddressResponse.toAddress(): String? {
+    val firstDocument = documents.firstOrNull() ?: return null
+    return firstDocument.roadAddress?.addressName
+        ?: firstDocument.address.addressName
+}

Address도 nullable일 수 있다면 동일한 패턴을 적용해주면 더욱 안전합니다.

🧹 Nitpick comments (19)
core/designsystem/src/main/res/drawable/icon_down_arrow_black.xml (1)

1-16: 파일명 일관성 검토 필요: 다른 아이콘과 프리픽스 불일치

현재 파일명 icon_down_arrow_black.xmlicon_ 프리픽스를 사용하고 있으나, 같은 디렉토리의 다른 모든 드로어블 아이콘은 ic_ 프리픽스를 따르고 있습니다. (예: ic_water.xml, ic_camera.xml, ic_car.xml 등)

네이밍 컨벤션을 일관성 있게 맞추는 것을 권장합니다. ic_down_arrow_black.xml로 변경을 고려해주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/PhotoItem.kt (2)

32-39: 접근성을 위한 contentDescription 추가를 권장합니다.

이미지의 contentDescriptionnull로 설정되어 있습니다. 스크린 리더 사용자를 위해 "제보 사진" 등의 설명을 추가하는 것이 좋습니다. 또한, AsyncImage의 에러 상태나 로딩 상태에 대한 처리를 추가하면 사용자 경험이 개선됩니다.

다음과 같이 개선할 수 있습니다:

 AsyncImage(
     model = uri,
-    contentDescription = null,
+    contentDescription = "제보 사진",
     contentScale = ContentScale.Crop,
+    error = painterResource(id = R.drawable.ic_error_placeholder), // 에러 시 표시할 이미지
     modifier = Modifier
         .fillMaxSize()
         .clip(RoundedCornerShape(8.dp)),
 )

56-57: Preview에서 실제 표시 가능한 이미지 사용을 권장합니다.

빈 문자열로 생성된 Uri는 Preview에서 실제 이미지를 표시하지 못합니다. Preview의 유용성을 높이기 위해 drawable 리소스나 placeholder를 사용하는 것을 고려해보세요.

다음과 같이 개선할 수 있습니다:

 @Preview
 @Composable
 private fun Preview() {
     PhotoItem(
-        uri = "".toUri(),
+        uri = "android.resource://com.threegap.bitnagil/drawable/sample_image".toUri(),
         modifier = Modifier.background(color = BitnagilTheme.colors.orange50),
         onRemove = {},
     )
 }
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportField.kt (2)

14-41: 재사용성 향상을 위해 선택적 필드 지원을 고려해보세요.

현재 모든 필드에 필수 표시(*)가 항상 표시됩니다. 제보 기능에서 모든 필드가 필수라면 문제없지만, 향후 선택적 필드가 추가될 가능성을 고려하면 파라미터로 제어할 수 있으면 좋습니다.

다음과 같이 isRequired 파라미터를 추가할 수 있습니다:

 @Composable
 fun ReportField(
     title: String,
     modifier: Modifier = Modifier,
+    isRequired: Boolean = true,
     content: @Composable () -> Unit,
 ) {
     Column(
         modifier = modifier,
         verticalArrangement = Arrangement.spacedBy(8.dp),
     ) {
         Row(
             verticalAlignment = Alignment.CenterVertically,
             horizontalArrangement = Arrangement.spacedBy(2.dp),
         ) {
             Text(
                 text = title,
                 color = BitnagilTheme.colors.coolGray10,
                 style = BitnagilTheme.typography.body2SemiBold,
             )
+            if (isRequired) {
                 Text(
                     text = "*",
                     color = BitnagilTheme.colors.error10,
                     style = BitnagilTheme.typography.body1SemiBold,
                 )
+            }
         }
         content()
     }
 }

22-22: 디자인 시스템 토큰 사용을 고려해보세요.

spacing 값(8.dp, 2.dp)이 하드코딩되어 있습니다. 디자인 시스템에 spacing 토큰이 있다면 일관성을 위해 사용하는 것이 좋습니다.

예시:

verticalArrangement = Arrangement.spacedBy(BitnagilTheme.spacing.small),
// 또는
horizontalArrangement = Arrangement.spacedBy(BitnagilTheme.spacing.xSmall),

Also applies to: 26-26

core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextField.kt (1)

46-53: modifier.fillMaxWidth() 순서 조정이 필요합니다.

현재 modifier = modifier.fillMaxWidth() 구조라 호출 측에서 Modifier.width(...) 등을 전달해도 마지막에 fillMaxWidth()가 덮어써 폭이 항상 확장됩니다. 디자인 시스템 컴포넌트라면 기본값으로 전폭을 채우되, 필요 시 호출자가 다른 크기를 지정할 수 있어야 합니다. Modifier.fillMaxWidth()를 먼저 적용한 뒤 then(modifier)로 이어 붙이면 기본 동작은 유지하면서도 확장성을 확보할 수 있습니다.

-        modifier = modifier
-            .fillMaxWidth()
+        modifier = Modifier
+            .fillMaxWidth()
+            .then(modifier)
             .background(
                 color = BitnagilTheme.colors.coolGray99,
                 shape = RoundedCornerShape(12.dp),
             )
data/src/main/java/com/threegap/bitnagil/data/file/model/request/FileInfoRequestDto.kt (1)

1-12: 파일 정보 DTO 정의가 직관적입니다

prefix, fileName 두 필드만으로 presigned URL 요청에 필요한 정보가 잘 표현되고 있고, @Serializable 설정도 적절합니다. 현재 JSON 키와 프로퍼티명이 동일하므로 @SerialName은 없어도 동작하겠지만, 서버 스펙 변경에 대비한 명시적 매핑 용도라면 유지해도 괜찮아 보입니다.

data/src/main/java/com/threegap/bitnagil/data/file/datasource/FileDataSource.kt (1)

1-12: presigned URL 조회 인터페이스는 명확합니다 (타입 래핑은 선택 사항)

KDoc으로 계약이 잘 문서화되어 있고, Result<Map<String, String>> 형태도 간단하게 쓰기 좋습니다. 다만 key: S3 경로, value: presigned URL 의미가 고정이라면, 추후를 위해 별도 타입(예: PresignedUrlMap 타입 alias 또는 data class)으로 한 번 래핑해 두면 오용을 줄이는 데 도움이 될 수 있습니다. 지금 단계에서는 선택 사항입니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategorySelector.kt (1)

53-60: Preview에서 선택된 상태도 보여주면 좋을 것 같습니다.

현재 Preview는 기본 텍스트("카테고리 선택")를 사용하고 있어 선택되지 않은 상태만 보여줍니다. 실제 카테고리가 선택된 상태의 Preview도 추가하면 컴포넌트의 두 가지 상태를 모두 확인할 수 있습니다.

다음과 같이 두 가지 상태의 Preview를 추가할 수 있습니다:

@Preview
@Composable
private fun PreviewUnselected() {
    ReportCategorySelector(
        title = null,
        onClick = {},
    )
}

@Preview
@Composable
private fun PreviewSelected() {
    ReportCategorySelector(
        title = "보도블록 파손",
        onClick = {},
    )
}
domain/src/main/java/com/threegap/bitnagil/domain/report/model/ReportCategory.kt (1)

3-29: 문자열 매핑이 enum 이름과 완전히 동일해 다소 중복 코드입니다.

백엔드에서 사용하는 문자열이 enum 이름과 정확히 동일하다면,

  • fromStringenumValueOf<ReportCategory>(value)
  • toStringvalue.name

형태로 단순화해서 중복을 줄이는 것도 고려해 볼 수 있습니다.

반대로, 서버/클라이언트 간 매핑 규칙이 나중에 달라질 가능성을 염두에 두고 현재처럼 명시적으로 유지하고 싶다면 이 구현도 충분히 괜찮습니다. 그 경우에는 enum 항목을 추가/변경할 때 companion 의 when 분기도 반드시 함께 갱신해 주어야 합니다.

data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/LocationDataSourceImpl.kt (1)

14-22: 위치 조회를 Result 로 감싼 구조는 좋지만, 예외 타입을 구체화하면 더 다루기 쉬울 것 같습니다.

lastLocation.await() 결과가 null 인 경우에 단순 Exception("Location not available") 을 던지고 있는데,

  • 별도의 도메인 예외 타입 (예: LocationNotAvailableException)
  • 혹은 IllegalStateException 등 보다 의미가 드러나는 표준 예외

로 구분해 두면 상위 레이어에서 isFailure 인 경우를 처리할 때, 어떤 실패인지 분기하기가 더 수월해집니다. 현재 구현도 동작에는 문제 없지만, 예외 타입을 명확히 해두면 이후 에러 핸들링 확장에 도움이 될 것 같습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/CurrentLocationInput.kt (1)

29-67: Row 의 modifier 를 Box 에 재사용하면 레이아웃이 의도치 않게 깨질 수 있습니다.

현재 Row 에 전달된 modifierBox 에도 그대로 넘기고 있어서, 호출부에서 예를 들어 Modifier.fillMaxWidth() 를 넘기면 Row 전체뿐 아니라 오른쪽 아이콘 영역 Box 에도 동일하게 적용됩니다. 이 경우 Row 내부에서 아이콘 영역이 과도하게 넓어지거나, 의도치 않은 패딩/사이즈가 중복 적용되는 등 레이아웃 부작용이 생길 수 있습니다.

Box 쪽은 독립적인 레이아웃을 가지는 게 자연스러워 보이므로, 아래처럼 Modifier 로 시작하는 편이 안전합니다.

-        Box(
-            modifier = modifier
+        Box(
+            modifier = Modifier
                 .background(
                     color = BitnagilTheme.colors.orange500,
                     shape = RoundedCornerShape(12.dp),
                 )
                 .fillMaxHeight()
                 .padding(14.dp)
                 .clickableWithoutRipple(
                     onClick = { onClick() },
                 ),

또한 이 Box 가 "현재 위치 검색" 액션을 수행하는 버튼 역할을 하기 때문에, 추후에라도 semantics { contentDescription = "현재 위치 검색" } 와 같은 접근성 정보를 추가해 두면 스크린 리더 사용자 입장에서도 의미 파악이 더 쉬워질 것입니다.

domain/src/main/java/com/threegap/bitnagil/domain/address/repository/AddressRepository.kt (1)

5-7: 주소 반환 타입 확장 여지

도메인 레벨에서 주소를 String 으로만 노출하면 추후 도로명/지번/행정동 코드 등 메타데이터가 늘어날 때 확장성이 떨어질 수 있습니다. 지금은 충분하지만, 나중에 요구사항이 늘어나면 별도의 Address VO를 도입하는 것도 고려해보셔도 좋겠습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/CompleteReportCard.kt (1)

96-114: 이미지 목록이 비어 있을 때의 UX 보완 제안

카테고리/주소는 "... 없음" 문구로 처리해 주셨는데, 이미지가 없을 때는 빈 LazyRow 만 보여서 사용자가 “정상으로 불러온 건가?” 헷갈릴 수 있습니다.

images.isEmpty() 인 경우 "제보 사진 없음" 같은 텍스트를 보여주고, 있을 때만 LazyRow 를 노출하는 식으로 분기하면 일관성이 더 좋아질 것 같습니다.

data/src/main/java/com/threegap/bitnagil/data/file/uploader/ImageUploader.kt (1)

18-38: 전체 업로드 처리 흐름은 안정적으로 보입니다

  • IO 디스패처 전환, execute().use {}로 응답 자원 정리까지 모두 적절합니다.
  • 다만 실패 시 IOException("Upload failed: code")만 던져 상위 레이어에서는 오류 원인을 파악하기 어렵습니다. 필요하다면 errorBody 일부를 예외 메시지에 포함하거나, 공통 TAG 상수로 Log 태그를 정리해 디버깅을 조금 더 쉽게 만드는 것도 고려해 볼 만합니다.
app/src/main/AndroidManifest.xml (1)

6-10: 카메라 필수 선언(android:required="true") 의도가 맞는지 한 번만 확인해주세요

  • 권한 추가(CAMERA, 위치)와 FileProvider 설정 자체는 표준 패턴이라 문제 없어 보입니다.
  • 다만 uses-feature android.hardware.cameraandroid:required="true"를 주면 카메라가 없는 기기(일부 태블릿/Chromebook 등)에는 아예 설치가 불가능합니다.
    앱이 그런 기기를 전부 지원 대상에서 배제해도 된다면 괜찮지만, 설치는 허용하되 카메라 기능만 비활성화하고 싶다면 required="false"로 두고 런타임에서 카메라 사용 가능 여부를 체크하는 방식도 고려할 수 있습니다.

Also applies to: 50-58

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/CompleteReportContent.kt (1)

30-105: 구현 자체는 깔끔하지만, 텍스트 리소스 분리와 버튼 속성 정리가 더 좋을 수 있습니다

  • 전체 레이아웃/스크롤 구조, CompleteReportCarduiState를 그대로 넘기는 흐름은 자연스럽습니다.
  • 다만 "제보가 완료되었습니다.", "빛나길에서 접수 후 완료되면\n신고를 진행합니다.", "확인" 등의 하드코딩된 문자열은 stringResource + R.string.*로 분리해 두면 추후 카피 수정이나 다국어 지원 시 훨씬 관리가 수월합니다.
  • BitnagilTextButton에서 enabled = true로 고정인데 disabledBackgroundColor/disabledTextColor만 커스터마이즈되어 있어 현재로서는 효과가 없습니다. 이후 상태 연동 계획이 없다면 필요할 때 추가하는 쪽으로 단순화해도 좋습니다.
data/src/main/java/com/threegap/bitnagil/data/report/model/request/ReportRequestDto.kt (1)

8-35: 도메인 → DTO 매핑 구조가 명확하고 문제 없어 보입니다

  • ReportRequestDto 필드 구성과 Report.toDto()의 매핑이 직관적이고, 서버 계약만 맞다면 별다른 리스크는 없어 보입니다.
  • 코드를 조금 더 간결하게 하고 싶다면 아래처럼 표현식 본문으로 정리하는 정도만 선택 사항으로 고려해 볼 수 있습니다.
fun Report.toDto() = ReportRequestDto(
    reportTitle = title,
    reportContent = content,
    reportCategory = ReportCategory.toString(category),
    reportImageUrls = imageUrls,
    reportLocation = address,
    latitude = latitude,
    longitude = longitude,
)
data/src/main/java/com/threegap/bitnagil/data/address/repositoryImpl/AddressRepositoryImpl.kt (1)

15-24: 위임 구조는 적절하며, 주소 없음 케이스만 명시적으로 구분하면 더 좋습니다

  • LocationDataSource/AddressDataSource에 그대로 위임하고, 응답을 Result로 감싸는 구조는 깔끔합니다.
  • toAddress()가 null을 반환할 때 일반 Exception("Address not found")을 던지면 상위 레이어에서 다른 네트워크 오류와 구분하기 어려울 수 있습니다. 필요하다면 AddressNotFoundException 같은 도메인 전용 예외나, Result.failure(DomainError.AddressNotFound) 형태로 구분 가능한 에러 타입을 도입하는 것도 고려해 볼 만합니다.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91613b7 and b602bee.

📒 Files selected for processing (82)
  • app/build.gradle.kts (1 hunks)
  • app/src/main/AndroidManifest.xml (2 hunks)
  • app/src/main/java/com/threegap/bitnagil/MainNavHost.kt (3 hunks)
  • app/src/main/java/com/threegap/bitnagil/Route.kt (1 hunks)
  • app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt (3 hunks)
  • app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt (2 hunks)
  • app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt (2 hunks)
  • app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt (2 hunks)
  • app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt (2 hunks)
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilFloatingButton.kt (3 hunks)
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1 hunks)
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextField.kt (1 hunks)
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt (5 hunks)
  • core/designsystem/src/main/res/drawable/ic_btn_close_sm.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_camera.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_camera_gray.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_car.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_complaint.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_hammer.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_light.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_loading.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_location.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_outside_gray.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_report.xml (0 hunks)
  • core/designsystem/src/main/res/drawable/ic_water.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/icon_down_arrow_black.xml (1 hunks)
  • core/network/build.gradle.kts (1 hunks)
  • core/network/src/main/java/com/threegap/bitnagil/network/Qualifier.kt (2 hunks)
  • data/build.gradle.kts (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/address/datasource/AddressDataSource.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/address/datasource/LocationDataSource.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/AddressDataSourceImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/address/datasourceImpl/LocationDataSourceImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/address/di/LocationModule.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/address/model/response/Coord2AddressResponse.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/address/repositoryImpl/AddressRepositoryImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/address/service/AddressService.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/file/datasource/FileDataSource.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/file/datasourceImpl/FileDataSourceImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/file/model/request/FileInfoRequestDto.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/file/repositoryImpl/FileRepositoryImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/file/service/FileService.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/file/uploader/ImageUploader.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/report/datasource/ReportDataSource.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/report/datasourceImpl/ReportDataSourceImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/report/model/request/ReportRequestDto.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/report/repositoryImpl/ReportRepositoryImpl.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/report/service/ReportService.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/address/model/CurrentAddress.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/address/model/CurrentLocation.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/address/repository/AddressRepository.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/address/usecase/FetchCurrentAddressUseCase.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/file/model/ImageFile.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/file/repository/FileRepository.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/file/usecase/UploadReportImagesUseCase.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/report/model/Report.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/report/model/ReportCategory.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/report/repository/ReportRepository.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/report/usecase/SubmitReportUseCase.kt (1 hunks)
  • gradle/libs.versions.toml (3 hunks)
  • presentation/build.gradle.kts (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/common/file/ImageFileConverter.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/common/premission/PermissionHandler.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/common/premission/RememberPermissionHandler.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/ReportScreen.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/ReportViewModel.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/AddPhotoButton.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/CompleteReportCard.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/CurrentLocationInput.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ImageSourceBottomSheet.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/PhotoItem.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategoryBottomSheet.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategorySelector.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportField.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/CompleteReportContent.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/SubmittingReportContent.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/model/ReportCategoryExtension.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/model/ReportSideEffect.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/model/ReportState.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/model/SubmitState.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/setting/SettingScreen.kt (2 hunks)
  • presentation/src/main/res/xml/file_paths.xml (1 hunks)
💤 Files with no reviewable changes (1)
  • core/designsystem/src/main/res/drawable/ic_report.xml
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-03T09:05:30.067Z
Learnt from: wjdrjs00
Repo: YAPP-Github/Bitnagil-Android PR: 16
File: core/network/src/main/java/com/threegap/bitnagil/network/auth/TokenAuthenticator.kt:12-46
Timestamp: 2025-07-03T09:05:30.067Z
Learning: 이 프로젝트에서는 네트워크 모듈을 점진적으로 개발하고 있으며, TokenAuthenticator 같은 인증 관련 기능은 실제 API 연동 작업 시점에 NetworkModule에 연결할 예정입니다.

Applied to files:

  • app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt
📚 Learning: 2025-07-21T10:38:49.104Z
Learnt from: l5x5l
Repo: YAPP-Github/Bitnagil-Android PR: 38
File: presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/textbutton/TextButton.kt:30-35
Timestamp: 2025-07-21T10:38:49.104Z
Learning: presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/textbutton/TextButton.kt의 TextButton 컴포넌트는 임시로 구현된 컴포넌트로, 디자인 시스템 구현시 대체 예정입니다.

Applied to files:

  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilFloatingButton.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/AddPhotoButton.kt
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextField.kt
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt
🧬 Code graph analysis (16)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategorySelector.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/SubmittingReportContent.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ImageSourceBottomSheet.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
data/src/main/java/com/threegap/bitnagil/data/report/datasourceImpl/ReportDataSourceImpl.kt (1)
data/src/main/java/com/threegap/bitnagil/data/common/SafeApiCall.kt (1)
  • safeApiCall (10-25)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/ReportScreen.kt (14)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/ReportViewModel.kt (1)
  • navigateToBack (115-119)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/premission/RememberPermissionHandler.kt (1)
  • rememberPermissionHandler (20-92)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ImageSourceBottomSheet.kt (1)
  • ImageSourceBottomSheet (25-79)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategoryBottomSheet.kt (1)
  • ReportCategoryBottomSheet (32-78)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/SubmittingReportContent.kt (1)
  • SubmittingReportContent (21-58)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/CompleteReportContent.kt (1)
  • CompleteReportContent (30-105)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilTopBar.kt (1)
  • BitnagilTopBar (22-63)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportField.kt (1)
  • ReportField (14-41)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/AddPhotoButton.kt (1)
  • AddPhotoButton (22-62)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/PhotoItem.kt (1)
  • PhotoItem (23-51)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextField.kt (1)
  • BitnagilTextField (25-73)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategorySelector.kt (1)
  • ReportCategorySelector (21-51)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/CurrentLocationInput.kt (1)
  • CurrentLocationInput (23-68)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1)
  • BitnagilTextButton (34-85)
app/src/main/java/com/threegap/bitnagil/MainNavHost.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/ReportScreen.kt (1)
  • ReportScreenContainer (76-201)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/CurrentLocationInput.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/setting/SettingScreen.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt (1)
  • BitnagilAlertDialog (23-88)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/PhotoItem.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIconButton (35-58)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/CompleteReportContent.kt (3)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/CompleteReportCard.kt (1)
  • CompleteReportCard (28-116)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1)
  • BitnagilTextButton (34-85)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/premission/RememberPermissionHandler.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt (1)
  • BitnagilAlertDialog (23-88)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategoryBottomSheet.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/AddPhotoButton.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/report/ReportViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/file/ImageFileConverter.kt (1)
  • convertUriToImageFile (36-58)
data/src/main/java/com/threegap/bitnagil/data/file/datasourceImpl/FileDataSourceImpl.kt (1)
data/src/main/java/com/threegap/bitnagil/data/common/SafeApiCall.kt (1)
  • safeApiCall (10-25)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1)
  • BitnagilTextButton (34-85)
🪛 detekt (1.23.8)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/file/ImageFileConverter.kt

[warning] 55-55: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (58)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilFloatingButton.kt (3)

20-20: 필요한 import 추가로 적절합니다.

width() 수정자 사용을 위한 필수 import로 문제없습니다.


104-106: 메뉴 컨테이너 패딩 변경 사항을 설계 사양과 확인하세요.

외부 Column의 패딩이 수직(16.dp) 전용으로 변경되었습니다. 이는 가로 패딩을 개별 Row 항목 레벨로 이동시킵니다. 이 변경이 제보하기 기능의 FAB 메뉴 설계 사양과 일치하는지 확인하세요.


172-179: 고정 너비(112.dp)로 인한 텍스트 잘림 위험을 확인하세요.

FloatingActionMenuItem Row에 padding(horizontal = 16.dp)width(112.dp)가 추가되어 텍스트의 가용 폭이 약 42dp로 제한됩니다(icon 24dp + spacing 14dp 제외). body2Medium 스타일의 메뉴 텍스트가 이 너비 내에서 제대로 표시되는지, 특히 제보하기 기능의 실제 메뉴 항목으로 테스트하여 텍스트 잘림이나 줄바꿈 문제가 없는지 확인하세요.

core/designsystem/src/main/res/drawable/ic_location.xml (1)

1-38: 잘 작성된 위치 핀 아이콘 자산입니다.

XML 구조가 올바르고 경로 정의가 명확합니다. 투명 배경에 흰색 스트로크와 채우기를 사용하여 일관된 디자인 시스템 스타일을 따릅니다.

core/designsystem/src/main/res/drawable/ic_camera_gray.xml (1)

1-23: 회색 카메라 아이콘이 제대로 구성되어 있습니다.

여러 경로의 계층화를 통해 아웃라인과 세부 정보를 표현합니다. 디자인 시스템의 회색 색상(#46474C)과 흰색 악센트를 일관되게 사용합니다.

core/designsystem/src/main/res/drawable/ic_outside_gray.xml (1)

1-33: 회색 아이콘 자산의 구조가 정확합니다.

여러 경로를 계층화하여 세부 표현을 만듭니다. 스트로크 속성(lineWidth, lineCap, lineJoin)이 모두 올바르게 설정되어 있습니다.

core/designsystem/src/main/res/drawable/ic_light.xml (1)

1-29: 조명 아이콘이 잘 설계되어 있습니다.

노란색(#FFDB12) 채우기와 회색 스트로크의 조합으로 명확한 시각적 계층을 만듭니다. 클립 경로 사용과 그룹 구조가 적절합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/SubmittingReportContent.kt (1)

21-58: 전반적인 구현이 깔끔합니다.

Jetpack Compose 패턴을 잘 따르고 있으며, 테마를 적절히 활용하고 있습니다. 레이아웃 구조도 명확하고 유지보수하기 좋습니다.

core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1)

98-112: 구현이 올바르며 실제 사용 사례가 확인되었습니다.

코드베이스 검증 결과, 추가된 매개변수화된 default() 시그니처가 다음 위치에서 적극적으로 활용되고 있습니다:

  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/template/CompleteReportContent.kt (lines 95-98)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/report/ReportScreen.kt (lines 339-342)

두 곳 모두 제보하기 기능에서 비활성화 상태의 색상을 커스터마이징하고 있으며, 나머지 매개변수는 기본값을 사용합니다. 또한 기존의 매개변수 없는 default() 호출도 정상적으로 작동하여 후방 호환성이 완벽하게 유지됩니다.

이 설계는 불필요한 일반화가 아니라 실제 요구사항을 충족하는 합리적인 구현입니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/PhotoItem.kt (1)

41-49: 코드는 의도된 디자인입니다. 수정이 필요하지 않습니다.

검증 결과, offset(x = 10.dp, y = (-10).dp)로 인해 닫기 버튼이 64dp Box 경계를 벗어나는 것은 의도된 디자인입니다. 이는 모서리 버튼의 일반적인 UI 패턴으로, 코드베이스의 다른 컴포넌트(EmotionLoadingView.kt)에서도 동일한 패턴(align(Alignment.TopEnd) + offset)이 사용되고 있습니다. 버튼이 이미지 모서리에 걸쳐서 표시되도록 설계된 것으로, 접근성과 시각적 배치를 고려한 설계 시스템의 일부입니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportField.kt (2)

1-12: 패키지 선언과 import 구문이 적절합니다.

모든 import가 사용되고 있으며, 표준 Compose 라이브러리와 디자인 시스템에서 필요한 항목들만 포함되어 있습니다.


43-47: Preview 함수가 적절합니다.

한글 텍스트로 실제 사용 사례를 잘 보여주고 있으며, private으로 적절히 캡슐화되어 있습니다.

domain/src/main/java/com/threegap/bitnagil/domain/address/model/CurrentAddress.kt (1)

3-7: LGTM!

도메인 모델이 깔끔하게 구현되었습니다. 불변 속성으로 구성되어 있고 단순명료합니다.

domain/src/main/java/com/threegap/bitnagil/domain/address/model/CurrentLocation.kt (1)

3-6: LGTM!

위치 정보를 담는 깔끔한 도메인 모델입니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ImageSourceBottomSheet.kt (3)

36-45: LGTM!

바텀시트를 숨긴 후 액션을 실행하는 로직이 올바르게 구현되었습니다. invokeOnCompletion을 사용하여 시트가 완전히 숨겨진 후 콜백을 실행하는 것이 좋습니다.


47-79: LGTM!

바텀시트 UI가 깔끔하게 구현되었습니다. 디자인 시스템의 색상과 타이포그래피를 잘 활용하고 있습니다.


81-101: LGTM!

옵션 아이템 컴포넌트가 재사용 가능하고 깔끔하게 구현되었습니다.

core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilAlertDialog.kt (1)

25-88: LGTM!

다이얼로그를 디자인 시스템으로 이동하고 모든 텍스트를 파라미터화하여 재사용 가능하도록 개선한 것이 좋습니다. 이제 다양한 상황에서 활용할 수 있습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/common/file/ImageFileConverter.kt (2)

15-34: LGTM!

파일 이름 추출 로직이 견고하게 구현되었습니다. 콘텐츠 리졸버에서 이름을 가져올 수 없는 경우 타임스탬프와 UUID를 사용한 폴백 로직이 적절합니다.


60-64: LGTM!

카메라 이미지 URI 생성 로직이 올바르게 구현되었습니다. 캐시 디렉토리를 사용하고 FileProvider를 통해 URI를 생성하는 것이 적절합니다.

data/build.gradle.kts (1)

21-22: LGTM!

위치 기능 구현에 필요한 의존성이 적절하게 추가되었습니다. play-services-locationkotlinx-coroutines-play는 위치 데이터 소스 구현에 필수적입니다.

core/network/src/main/java/com/threegap/bitnagil/network/Qualifier.kt (1)

1-15: LGTM!

패키지 경로가 올바르게 수정되었고, Kakao API 연동을 위한 @Kakao 한정자가 적절하게 추가되었습니다. 기존 한정자들과 일관된 패턴을 따르고 있습니다.

core/network/build.gradle.kts (1)

16-16: LGTM!

DI 한정자 어노테이션 사용을 위해 javax.inject 의존성이 적절하게 추가되었습니다. Qualifier.kt@Qualifier 어노테이션 사용에 필요한 의존성입니다.

presentation/build.gradle.kts (1)

23-23: 변경사항 승인

Accompanist Permissions 라이브러리 추가가 적절합니다. 권한 처리 UI 구현을 위한 표준 라이브러리입니다.

presentation/src/main/res/xml/file_paths.xml (1)

1-5: FileProvider 설정 확인

카메라 이미지 캡처를 위한 FileProvider 경로 설정이 올바르게 구성되어 있습니다.

domain/src/main/java/com/threegap/bitnagil/domain/report/model/Report.kt (1)

3-11: 도메인 모델 검증 로직 고려

Report 도메인 모델의 필드들에 대한 검증 요구사항을 확인하세요:

  • title, content: 길이 제한이나 빈 값 체크가 필요한가요?
  • latitude, longitude: 유효한 좌표 범위(-9090, -180180) 검증이 필요한가요?
  • imageUrls: 빈 리스트가 허용되나요?

비즈니스 규칙에 따라 UseCase나 ViewModel 레벨에서 검증을 수행하거나, 도메인 모델에 팩토리 메서드를 추가하는 것을 고려하세요.

app/build.gradle.kts (1)

50-59: Kakao REST API 키 설정 확인

기존 KAKAO_NATIVE_APP_KEY 패턴과 일관되게 구현되어 있습니다. 환경 변수 및 local.properties 파일에서 키를 가져오는 로직이 적절합니다.

app/src/main/java/com/threegap/bitnagil/di/data/RepositoryModule.kt (1)

67-77: DI 바인딩 추가 확인

세 개의 새로운 Repository 바인딩이 기존 패턴과 일관되게 추가되었습니다. Singleton 스코프 적용이 적절합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/model/SubmitState.kt (1)

3-3: 제출 상태 enum 확인

제보 제출 라이프사이클을 나타내는 세 가지 상태가 적절하게 정의되어 있습니다.

app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt (2)

46-46: 네비게이션 파라미터 추가 확인

제보 화면으로의 네비게이션 콜백이 기존 패턴과 일관되게 추가되었습니다.


110-114: 제보하기 FAB 아이콘 리소스 확인 완료

ic_complaint 아이콘 리소스가 core/designsystem/src/main/res/drawable/ic_complaint.xml에 벡터 드로어블로 정상 정의되어 있으며, app 모듈이 core:designsystem에 의존하고 있어 R.drawable.ic_complaint 참조가 컴파일 시 올바르게 해석됩니다.

app/src/main/java/com/threegap/bitnagil/Route.kt (1)

52-53: Report Route 추가 확인

제보 화면을 위한 새로운 Route가 기존 패턴과 일관되게 추가되었습니다. Serializable data object 사용이 적절합니다.

data/src/main/java/com/threegap/bitnagil/data/report/datasource/ReportDataSource.kt (1)

1-7: 제보 전송 데이터소스 설계 적절해 보입니다

data 계층에서 DTO를 직접 받도록 분리한 구조와 Result<Long> 반환 시그니처 모두 일관되고 명확합니다. 현재 수준에서는 추가 수정 없이 사용해도 될 것 같습니다.

data/src/main/java/com/threegap/bitnagil/data/address/datasource/LocationDataSource.kt (1)

1-7: LocationDataSource 도메인 의존 방향이 좋습니다

data 계층이지만 반환 타입으로 CurrentLocation 도메인 모델을 사용하는 설계가 전체 레이어 구조와 잘 맞습니다. Result<CurrentLocation> 형태도 이후 usecase에서 다루기 편해 보여요.

domain/src/main/java/com/threegap/bitnagil/domain/file/usecase/UploadReportImagesUseCase.kt (1)

1-13: 이미지 업로드 usecase 위임 구조 괜찮습니다

operator fun invoke로 감싸서 uploadReportImagesUseCase(imageFiles) 형태로 사용할 수 있는 점이 가독성에 좋고, 책임도 FileRepository로 잘 위임된 것 같습니다. 현재 요구사항 기준에서는 별도 로직 없이 이 정도 얇은 usecase로도 충분해 보여요.

data/src/main/java/com/threegap/bitnagil/data/report/service/ReportService.kt (1)

1-13: 제보 API 시그니처가 계층 간 계약과 잘 맞습니다

ReportRequestDto를 바디로 받고 BaseResponse<Long>을 반환하는 구조가 ReportDataSource·ReportRepository에서 사용하는 Result<Long> 흐름과 자연스럽게 매핑될 수 있어 보입니다. 엔드포인트 경로(/api/v2/reports)도 의도가 분명해서 현재 PR 범위에서는 무리 없이 사용 가능해 보입니다.

domain/src/main/java/com/threegap/bitnagil/domain/report/repository/ReportRepository.kt (1)

1-7: 도메인 ReportRepository 추상화가 깔끔합니다

도메인 모델 Report를 인자로 받고 Result<Long>을 반환하는 단일 책임 인터페이스로 잘 정의되어 있습니다. 이후 구현체(ReportRepositoryImpl)와 usecase에서 사용하기에 시그니처가 직관적이고 확장 여지도 충분해 보입니다.

data/src/main/java/com/threegap/bitnagil/data/address/di/LocationModule.kt (1)

13-21: LGTM! 위치 서비스 DI 모듈 구현이 올바릅니다.

FusedLocationProviderClient를 싱글톤으로 제공하는 Hilt 모듈이 표준 패턴을 따르고 있으며, ApplicationContext를 올바르게 사용하고 있습니다.

data/src/main/java/com/threegap/bitnagil/data/file/service/FileService.kt (1)

8-13: LGTM! Retrofit 서비스 인터페이스가 올바르게 정의되었습니다.

Presigned URL을 가져오는 API 엔드포인트가 명확하게 정의되어 있으며, suspend 함수를 사용한 코루틴 지원이 적절합니다.

domain/src/main/java/com/threegap/bitnagil/domain/report/usecase/SubmitReportUseCase.kt (1)

7-13: LGTM! Use case 구현이 깔끔합니다.

Repository에 위임하는 단순한 구조로, operator invoke 패턴을 사용하여 Kotlin 관용구를 잘 따르고 있습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/setting/SettingScreen.kt (1)

56-63: LGTM! 디자인 시스템 컴포넌트로의 리팩토링이 적절합니다.

LogoutConfirmDialog에서 BitnagilAlertDialog로 교체하여 디자인 시스템 컴포넌트를 활용하는 것은 좋은 개선입니다. 모든 파라미터가 올바르게 전달되고 기존 로직이 유지됩니다.

domain/src/main/java/com/threegap/bitnagil/domain/file/repository/FileRepository.kt (1)

5-7: LGTM! 도메인 레이어 Repository 인터페이스가 명확합니다.

Result 래퍼를 사용한 에러 처리와 suspend 함수를 통한 비동기 처리가 적절하며, 깔끔한 도메인 추상화를 제공합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/model/ReportSideEffect.kt (1)

3-6: LGTM! MVI 패턴의 Side Effect가 올바르게 정의되었습니다.

Sealed interface와 data object를 사용한 side effect 모델링이 표준 MVI 패턴을 잘 따르고 있습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/common/premission/PermissionHandler.kt (1)

6-14: LGTM! 권한 처리 인터페이스가 잘 설계되었습니다.

@stable 어노테이션과 Composable 함수를 포함한 인터페이스 설계가 Compose의 권한 처리 패턴을 잘 추상화하고 있습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategorySelector.kt (1)

21-51: LGTM! 카테고리 선택 컴포넌트가 올바르게 구현되었습니다.

선택 여부에 따른 텍스트 색상 변경 로직이 명확하며, 디자인 시스템을 잘 활용하고 있습니다.

domain/src/main/java/com/threegap/bitnagil/domain/file/model/ImageFile.kt (1)

11-37: ByteArray 필드를 고려한 equals/hashCode 재정의가 적절합니다.

bytes 에 대해 contentEquals / contentHashCode 를 사용해서 값 기반 비교가 되도록 잘 구현되어 있습니다. 도메인 값 객체로서 필요한 동등성 정의가 명확하고, 특별한 버그 포인트는 없어 보입니다. bytes 가 mutable 이라 컬렉션의 key 로 사용한 뒤 내용을 변경하지 않도록만 주의하면 될 것 같습니다.

app/src/main/java/com/threegap/bitnagil/MainNavHost.kt (2)

96-144: HomeNavHost 에서 Report 화면으로의 네비게이션 연결이 기존 패턴과 잘 맞습니다.

navigateToReport 가 다른 화면들과 동일하게 navigator.navController.navigate(Route.Report) { launchSingleTop = true } 형태로 구현되어 있고, Route 기반 네비게이션 구조와도 일관성이 있어서 유지보수 측면에서도 무난해 보입니다.


299-307: Report 경로 컴포저블 정의가 다른 화면들과 동일한 back 처리 패턴을 사용합니다.

composable<Route.Report> 안에서 ReportScreenContainer 를 사용하고, 뒤로 가기 시 previousBackStackEntry 체크 후 popBackStack() 을 호출하는 패턴이 Setting/Emotion 등 다른 화면들과 동일해서, 네비게이션 동작이 예측 가능하고 안전합니다.

data/src/main/java/com/threegap/bitnagil/data/report/datasourceImpl/ReportDataSourceImpl.kt (1)

9-14: 구현이 깔끔합니다.

safeApiCall을 사용하여 API 호출을 적절하게 래핑하고 있으며, 단순하고 명확한 위임 패턴을 따르고 있습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/ReportScreen.kt (1)

76-201: 전체적인 구조와 권한 처리가 잘 구현되어 있습니다.

카메라/앨범/위치 권한 처리, 이미지 URI 상태 관리, 바텀시트 표시 로직이 모두 적절하게 구현되어 있습니다. AnimatedContent를 사용한 제출 상태 전환도 사용자 경험을 향상시키는 좋은 접근입니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/AddPhotoButton.kt (1)

22-62: 깔끔한 구현입니다.

이미지 카운트에 따른 활성화 상태 처리와 동적 색상 변경이 적절하게 구현되어 있습니다. AnnotatedString을 사용한 카운트 표시도 좋습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/report/model/ReportCategoryExtension.kt (1)

6-28: 확장 프로퍼티를 사용한 UI 매핑이 적절합니다.

모든 ReportCategory 값에 대해 UI 문자열과 아이콘이 명확하게 매핑되어 있으며, exhaustive when 표현식으로 컴파일 타임 안전성이 보장됩니다.

data/src/main/java/com/threegap/bitnagil/data/address/datasource/AddressDataSource.kt (1)

5-7: 인터페이스 정의가 명확합니다.

좌표 타입으로 Double을 사용하고, 에러 처리를 위해 Result 타입을 반환하는 것이 적절합니다.

data/src/main/java/com/threegap/bitnagil/data/report/repositoryImpl/ReportRepositoryImpl.kt (1)

9-15: 리포지토리 구현이 적절합니다.

도메인 모델을 DTO로 변환하여 데이터 소스에 위임하는 패턴이 명확하게 구현되어 있습니다.

data/src/main/java/com/threegap/bitnagil/data/address/service/AddressService.kt (1)

8-12: 파라미터 매핑이 올바릅니다. 수정이 필요 없습니다.

Kakao Local API 문서에 따르면 coord2address 엔드포인트는 x(경도), y(위도) 파라미터를 기대하며, 현재 코드의 매핑이 정확합니다. 예: x=126.9786567(경도), y=37.566826(위도)

app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt (1)

3-3: 새 Service DI 바인딩 구성 적절

AddressService 에 Kakao용 Retrofit, FileService/ReportService 에 인증용 Retrofit 을 주입하는 패턴이 기존 Service 들과 동일해서 이해하기 쉽고, DI 그래프 상에서도 무리 없어 보입니다.

Also applies to: 6-6, 9-9, 74-87

presentation/src/main/java/com/threegap/bitnagil/presentation/report/component/ReportCategoryBottomSheet.kt (1)

32-124: 카테고리 바텀시트 구현 전반적으로 안정적

선택 시 onSelected 호출 후 시트를 숨기고, 애니메이션 완료 시 onDismiss 를 호출하는 흐름이 명확해서 상태 관리가 깔끔합니다. 리스트/아이템 구성도 ReportCategory 확장 프로퍼티들을 잘 활용하고 있어 재사용성이 좋아 보입니다.

domain/src/main/java/com/threegap/bitnagil/domain/address/usecase/FetchCurrentAddressUseCase.kt (1)

10-24: ****

이 리뷰 의견은 잘못되었습니다. Result.mapCatching은 인라인 함수이며, 컴파일러가 비-서스펜드 람다가 존재하지 않음을 알고 있으므로 인라인 람다 내에서 suspend 호출을 허용합니다. 이 경우 invoke() 함수가 suspend이므로, 인라인 람다 내의 서스펜딩 함수 호출은 허용됩니다.

따라서 현재 코드는 컴파일 오류가 발생하지 않으며 정상적으로 작동합니다. 제안된 명시적 Result 분기 방식은 선택적 리팩토링이지만, 코드의 컴파일 가능성 측면에서는 변경이 필수적이지 않습니다.

Likely an incorrect or invalid review comment.

Comment thread app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt
Copy link
Copy Markdown
Contributor

@l5x5l l5x5l left a comment

Choose a reason for hiding this comment

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

고생 많으셨습니다! 🚀

@wjdrjs00 wjdrjs00 merged commit ae7206f into develop Nov 21, 2025
2 checks passed
@wjdrjs00 wjdrjs00 deleted the feature/#139-report branch November 21, 2025 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 구현 🧤 대현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 제보하기 기능을 구현합니다.

2 participants