Feat/#95: ActionSheet 기반 카메라/갤러리 사진 선택 기능 추가#97
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- UploadUiState에 showPhotoPickerSheet 필드 추가 - UploadIntent에 UpdatePhotoPickerSheetVisibility 클래스 추가 - UploadViewModel에 핸들러 추가로 exhaustive when 조건 만족 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 50 minutes and 40 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
Walkthrough이 PR은 사진 선택 옵션을 확장하는 기능을 추가합니다. 카메라 권한을 선언하고, 하단 시트 기반 액션 시트 컴포넌트를 도입하며, 갤러리 아이콘을 추가하고, 업로드 화면을 수정하여 카메라로 촬영하거나 갤러리에서 선택하는 두 가지 옵션을 제공합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UploadScreen
participant ActionSheet
participant PermissionManager
participant MediaStore
participant Camera
participant UploadViewModel
User->>UploadScreen: 카메라 아이콘 클릭
UploadScreen->>UploadViewModel: UpdatePhotoPickerSheetVisibility(true)
UploadViewModel->>UploadScreen: showPhotoPickerSheet = true
UploadScreen->>ActionSheet: ActionSheet 표시
alt 카메라 선택
User->>ActionSheet: 카메라 옵션 선택
ActionSheet->>UploadScreen: 카메라 콜백 실행
UploadScreen->>PermissionManager: CAMERA 권한 검사
alt 권한 승인됨
PermissionManager->>MediaStore: 이미지 파일 사전 생성
MediaStore-->>UploadScreen: photoUri 반환
UploadScreen->>Camera: TakePicture 계약 실행
Camera->>Camera: 사용자 사진 촬영
Camera-->>UploadScreen: 촬영 결과
UploadScreen->>UploadViewModel: AddImages(캡처된 이미지)
else 권한 거부됨
PermissionManager-->>UploadScreen: 권한 거부
UploadScreen->>MediaStore: photoUri 삭제
end
else 갤러리 선택
User->>ActionSheet: 갤러리 옵션 선택
ActionSheet->>UploadScreen: 갤러리 콜백 실행
UploadScreen->>UploadViewModel: AddImages(선택된 이미지)
end
UploadScreen->>UploadViewModel: UpdatePhotoPickerSheetVisibility(false)
UploadViewModel->>UploadScreen: showPhotoPickerSheet = false
UploadScreen->>ActionSheet: ActionSheet 닫음
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
feature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadScreen.kt (1)
181-219:insertPhotoUri()/ 권한 처리 로직을 추출해 중복 제거 권장.카메라 클릭 핸들러(Line 188-203)와 권한 콜백(Line 145-156)에서 "권한 확인 → URI 삽입 → 런처 launch" 흐름이 거의 동일하게 반복됩니다. 한 함수로 추출하면 향후 권한 거부/실패 처리(위 코멘트)도 한 곳에서 관리할 수 있습니다.
♻️ 제안 수정안 스케치
+ val launchCamera: () -> Unit = { + val uri = insertPhotoUri() + if (uri != null) { + photoUri = uri + cameraLauncher.launch(uri) + } + } ... - if (hasCameraPermission) { - val uri = insertPhotoUri() - if (uri != null) { - photoUri = uri - cameraLauncher.launch(uri) - } - } else { - cameraPermissionLauncher.launch(Manifest.permission.CAMERA) - } + if (hasCameraPermission) launchCamera() + else cameraPermissionLauncher.launch(Manifest.permission.CAMERA)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadScreen.kt` around lines 181 - 219, The camera handling flow (permission check → insertPhotoUri() → set photoUri → cameraLauncher.launch) is duplicated between the camera click handler and the cameraPermissionLauncher callback; extract that logic into a single helper (e.g., ensureCameraUriAndLaunch or openCameraWithPermission) that: checks CAMERA permission, calls insertPhotoUri(), sets photoUri, and invokes cameraLauncher.launch(uri) or returns/handles failure; then replace both the onClick lambda in the ActionItem and the cameraPermissionLauncher callback to call this helper so all permission-failure and URI-failure handling is centralized.core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/ActionSheet.kt (2)
116-137: Preview 한계 안내(선택) — 모달 시트 미렌더 가능성.
ActionSheet은 내부적으로ModalBottomSheet기반의BuyOrNotBottomSheet를 사용하므로,@Preview에서는 시트 내용이 그려지지 않을 수 있습니다. 디자인 검토용으로는ActionItemRow만 따로 미리보는 보조 Preview를 추가하면 더 유용합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/ActionSheet.kt` around lines 116 - 137, ActionSheetPreview may not render the modal bottom sheet because ActionSheet uses BuyOrNotBottomSheet/ModalBottomSheet; add a separate Preview for the row component so designers can see the content: create a new `@Preview` composable that wraps a sample ActionItem (use the ActionItem data class and BuyOrNotIcons) and calls the ActionItemRow (or the exact row composable used inside ActionSheet) inside BuyOrNotTheme; keep ActionSheetPreview as-is (or annotate it with a note) but ensure the new preview references ActionItemRow, ActionItem, and BuyOrNotIcons so the row UI renders in the preview.
69-81:items(actions)확장 함수로 단순화 가능.
items(count = actions.size, key = { it })에서it은 인덱스(Int)이므로 사실상 키를 지정하지 않은 것과 동일합니다.androidx.compose.foundation.lazy.items확장을 사용하면 가독성도 좋아지고, 필요시key로 안정적인 식별자(text등)를 주기에도 명확합니다.♻️ 제안 수정안
-import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items ... - items( - count = actions.size, - key = { it }, - ) { index -> - val action = actions[index] - ActionItemRow( + items( + items = actions, + key = { it.text }, + ) { action -> + ActionItemRow( item = action, onClick = { action.onClick() hideSheet() }, ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/ActionSheet.kt` around lines 69 - 81, Replace the indexed items usage with the androidx.compose.foundation.lazy.items extension to simplify and clarify the loop in ActionSheet.kt: instead of items(count = actions.size, key = { it }) { index -> val action = actions[index] ... }, call items(actions, key = { action -> <stableId> }) { action -> ActionItemRow(item = action, onClick = { action.onClick(); hideSheet() }) }, using a stable identifier for the key (for example action.id or action.text) rather than the index.feature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadViewModel.kt (1)
69-78:NavigateBack처리 시showPhotoPickerSheet도 함께 초기화 권장.기존
NavigateBack은showExitDialog/showCategorySheet를 닫아주지만, 새로 추가된showPhotoPickerSheet는 초기화되지 않습니다. 시트가 열려 있는 상태에서 뒤로가기 흐름이 트리거되면 상태가 불일치할 수 있으니 일관성을 위해 함께 닫아주는 것이 좋습니다.♻️ 제안 수정안
UploadIntent.NavigateBack -> { updateState { - it.copy(showExitDialog = false, showCategorySheet = false) + it.copy( + showExitDialog = false, + showCategorySheet = false, + showPhotoPickerSheet = false, + ) } sendSideEffect(UploadSideEffect.NavigateBack) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadViewModel.kt` around lines 69 - 78, 현재 UploadIntent.NavigateBack 처리에서 updateState가 showExitDialog와 showCategorySheet만 false로 설정하고 있어 showPhotoPickerSheet가 닫히지 않는 문제입니다; UploadIntent.NavigateBack 분기에서 호출하는 updateState 블록에 showPhotoPickerSheet = false를 추가하여 상태를 일관되게 초기화하고 그 후 기존 sendSideEffect(UploadSideEffect.NavigateBack)를 그대로 호출하도록 수정하세요.app/src/main/AndroidManifest.xml (1)
6-6:<uses-feature>선언 누락 — 카메라 미탑재 기기 노출 차단 위험.
android.permission.CAMERA를 선언하면 Google Play가 암묵적으로android.hardware.camera기능을 요구하는 것으로 간주하여, 카메라가 없는 기기(태블릿/Chromebook 등)에서 앱이 설치 목록에 노출되지 않을 수 있습니다. 본 기능은 갤러리 선택 옵션으로 대체 가능하므로required="false"로 선언해 두는 것이 안전합니다.📱 제안 수정안
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.CAMERA" /> + <uses-feature + android:name="android.hardware.camera" + android:required="false" /> <uses-permission android:name="com.google.android.gms.permission.AD_ID" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/AndroidManifest.xml` at line 6, 현재 AndroidManifest에 android.permission.CAMERA만 선언되어 있어 Google Play가 암묵적으로 카메라 하드웨어를 요구하는 것으로 해석될 수 있으니, 앱이 카메라 미탑재 기기에도 노출되도록 manifest에 uses-feature 요소(<uses-feature android:name="android.hardware.camera" android:required="false"/>)를 추가하여 카메라를 선택적 기능으로 표시하고 기존의 <uses-permission android:name="android.permission.CAMERA"/> 선언은 유지하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@feature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadScreen.kt`:
- Around line 112-156: The photoUri stored with remember {
mutableStateOf<Uri?>(null) } can be lost across process death; change storage to
survive recreation by using rememberSaveable (serialize Uri via
Uri.toString()/Uri.parse) or move the pending URI into the UploadViewModel
(e.g., pendingCameraUri) so the camera flow keeps the URI between process
restarts; update places referencing photoUri (cameraPermissionLauncher callback
where insertPhotoUri() sets it, and cameraLauncher callback where
viewModel.handleIntent(UploadIntent.AddImages(listOf(it))) or
contentResolver.delete(it, ... ) is called) to use the new saved/restored value
consistently.
- Around line 145-156: The permission launcher (cameraPermissionLauncher)
silently ignores both denied permissions and insertPhotoUri() returning null, so
add user feedback and guidance: when granted == false show a Snackbar (or
dialog) informing the user that camera permission is required, and if
shouldShowRequestPermissionRationale indicates permanent denial, provide an
option to open app settings; likewise, when insertPhotoUri() returns null (both
inside cameraPermissionLauncher and the onClick handler where insertPhotoUri()
is called) show an error Snackbar explaining the failure to create the photo URI
and suggest retry/settings; use the existing cameraLauncher.launch(uri) flow
unchanged when uri is non-null and reuse a shared helper to show Snackbars to
avoid duplication.
---
Nitpick comments:
In `@app/src/main/AndroidManifest.xml`:
- Line 6: 현재 AndroidManifest에 android.permission.CAMERA만 선언되어 있어 Google Play가
암묵적으로 카메라 하드웨어를 요구하는 것으로 해석될 수 있으니, 앱이 카메라 미탑재 기기에도 노출되도록 manifest에 uses-feature
요소(<uses-feature android:name="android.hardware.camera"
android:required="false"/>)를 추가하여 카메라를 선택적 기능으로 표시하고 기존의 <uses-permission
android:name="android.permission.CAMERA"/> 선언은 유지하세요.
In
`@core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/ActionSheet.kt`:
- Around line 116-137: ActionSheetPreview may not render the modal bottom sheet
because ActionSheet uses BuyOrNotBottomSheet/ModalBottomSheet; add a separate
Preview for the row component so designers can see the content: create a new
`@Preview` composable that wraps a sample ActionItem (use the ActionItem data
class and BuyOrNotIcons) and calls the ActionItemRow (or the exact row
composable used inside ActionSheet) inside BuyOrNotTheme; keep
ActionSheetPreview as-is (or annotate it with a note) but ensure the new preview
references ActionItemRow, ActionItem, and BuyOrNotIcons so the row UI renders in
the preview.
- Around line 69-81: Replace the indexed items usage with the
androidx.compose.foundation.lazy.items extension to simplify and clarify the
loop in ActionSheet.kt: instead of items(count = actions.size, key = { it }) {
index -> val action = actions[index] ... }, call items(actions, key = { action
-> <stableId> }) { action -> ActionItemRow(item = action, onClick = {
action.onClick(); hideSheet() }) }, using a stable identifier for the key (for
example action.id or action.text) rather than the index.
In
`@feature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadScreen.kt`:
- Around line 181-219: The camera handling flow (permission check →
insertPhotoUri() → set photoUri → cameraLauncher.launch) is duplicated between
the camera click handler and the cameraPermissionLauncher callback; extract that
logic into a single helper (e.g., ensureCameraUriAndLaunch or
openCameraWithPermission) that: checks CAMERA permission, calls
insertPhotoUri(), sets photoUri, and invokes cameraLauncher.launch(uri) or
returns/handles failure; then replace both the onClick lambda in the ActionItem
and the cameraPermissionLauncher callback to call this helper so all
permission-failure and URI-failure handling is centralized.
In
`@feature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadViewModel.kt`:
- Around line 69-78: 현재 UploadIntent.NavigateBack 처리에서 updateState가
showExitDialog와 showCategorySheet만 false로 설정하고 있어 showPhotoPickerSheet가 닫히지 않는
문제입니다; UploadIntent.NavigateBack 분기에서 호출하는 updateState 블록에 showPhotoPickerSheet
= false를 추가하여 상태를 일관되게 초기화하고 그 후 기존
sendSideEffect(UploadSideEffect.NavigateBack)를 그대로 호출하도록 수정하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: f6e1704b-c591-4893-bd44-ba8c8b3fe39c
📒 Files selected for processing (8)
app/src/main/AndroidManifest.xmlcore/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/ActionSheet.ktcore/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/FeedCard.ktcore/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/icon/BuyOrNotIcons.ktcore/designsystem/src/main/res/drawable/ic_gallery.xmlfeature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadContract.ktfeature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadScreen.ktfeature/upload/src/main/java/com/sseotdabwa/buyornot/feature/upload/ui/UploadViewModel.kt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🛠 Related issue
closed #95
어떤 변경사항이 있었나요?
✅ CheckPoint
✏️ Work Description
😅 Uncompleted Tasks
📢 To Reviewers
ActionSheet는OptionSheet와 동일한BuyOrNotBottomSheet기반이나, 선택 상태 없이 액션 실행에 특화된 별도 컴포넌트입니다.showPhotoPickerSheet상태는 기존showCategorySheet패턴과 동일하게 ViewModel에서 관리합니다.📃 RCA 룰
Summary by CodeRabbit
릴리스 노트
새로운 기능
스타일