feat: 프로필 선택 BottomModal 컴포넌트 구현 - #49
Conversation
- 고정 높이보다 본문 콘텐츠가 크면 잘리는 이슈 해결 - title 2줄을 넘어갈 경우 상단이 시트 모서리에 붙어서 여백 확보
📝 WalkthroughWalkthrough프로필 이미지 선택 모델과 성별 토글, 이미지 캐러셀이 추가되었습니다. 선택 콘텐츠를 Changes프로필 이미지 선택 기능
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant ProfileImagePickerBottomSheet
participant ConfirmBottomSheet
participant ProfileImagePickerContent
participant ProfileImageCarousel
Caller->>ProfileImagePickerBottomSheet: 이미지 목록과 선택 상태 전달
ProfileImagePickerBottomSheet->>ConfirmBottomSheet: 콘텐츠와 저장/닫기 콜백 전달
ConfirmBottomSheet->>ProfileImagePickerContent: 본문 렌더링
ProfileImagePickerContent->>ProfileImageCarousel: 이미지 목록과 선택 콜백 전달
ProfileImageCarousel->>Caller: 선택된 이미지 전달
ConfirmBottomSheet->>Caller: 저장 또는 닫기 이벤트 전달
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 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 |
Gemini AI 코드리뷰안녕하세요, 시니어 Android 개발자로서 해당 PR에 대한 코드리뷰를 진행하겠습니다. 전반적으로 코드 품질, 구조, Jetpack Compose 모범 사례 준수 측면에서 매우 인상적인 PR입니다. 특히 상태 호이스팅, 책임 경계 명확화, 그리고 상세한 문서화는 훌륭합니다. 몇 가지 개선점을 제안드립니다.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt`:
- Around line 124-143: Update the title layout logic around isMultiLineTitle so
titles that wrap naturally due to available width receive the same multi-line
height, alignment, and padding as explicitly newline-separated titles. Prefer
using Text’s onTextLayout to derive whether the rendered line count exceeds one,
while preserving the existing single-line behavior and avoiding reliance on
title.contains('\n') alone.
- Around line 110-121: Update the height handling in ConfirmBottomSheet’s
heightModifier so fixed sheetHeight uses Modifier.heightIn(min = sheetHeight)
instead of a fixed height, allowing navigation bar and bottom padding insets to
expand the overall sheet while preserving the minimum height. Keep
wrapContentHeight() for the null sheetHeight case.
In
`@core/designsystem/src/main/java/kr/co/call/designsystem/component/profileimage/ProfileImageCarousel.kt`:
- Around line 215-220: Rename the compile-time constants SideScaleX, SideScaleY,
and SideDimAlpha to UPPER_SNAKE_CASE names, then update every reference in
ProfileImageCarousel, including the scaleX, scaleY, and dimAlpha calculations,
while preserving their existing values and behavior.
In
`@core/designsystem/src/main/java/kr/co/call/designsystem/component/profileimage/ProfileImageGenderToggle.kt`:
- Around line 128-155: Rename the Boolean selected parameter in
GenderSegmentLabel to isSelected and update its conditional usages plus the
named arguments at both call sites in ProfileImageGenderToggle.kt (lines 128-155
and 110,118). Also rename the local selected value in ProfileImagePageIndicator
to isSelected and update its usages in ProfileImageCarousel.kt (lines 181-209).
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2eaacee1-5e6a-40dd-803c-11b19c753f5d
📒 Files selected for processing (7)
core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.ktcore/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ProfileImagePickerBottomSheet.ktcore/designsystem/src/main/java/kr/co/call/designsystem/component/profileimage/ProfileImageCarousel.ktcore/designsystem/src/main/java/kr/co/call/designsystem/component/profileimage/ProfileImageGenderToggle.ktcore/designsystem/src/main/java/kr/co/call/designsystem/component/profileimage/ProfileImagePickerContent.ktcore/designsystem/src/main/java/kr/co/call/designsystem/component/profileimage/ProfileImagePickerModels.ktcore/designsystem/src/main/java/kr/co/call/designsystem/component/profileimage/ProfileImagePickerPreviewData.kt
| val isMultiLineTitle = title.contains('\n') | ||
| val headerHeight = if (isMultiLineTitle) 104.dp else 82.dp | ||
|
|
||
| Box( | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .height(82.dp), | ||
| .height(headerHeight), | ||
| ) { | ||
| Text( | ||
| text = title, | ||
| modifier = Modifier | ||
| .align(Alignment.BottomStart) | ||
| .padding(start = 16.dp, bottom = 24.dp), | ||
| .align( | ||
| if (isMultiLineTitle) Alignment.TopStart else Alignment.BottomStart, | ||
| ) | ||
| .padding( | ||
| start = 16.dp, | ||
| end = 56.dp, // 닫기 버튼과 겹치지 않도록 | ||
| top = if (isMultiLineTitle) 52.dp else 0.dp, | ||
| bottom = if (isMultiLineTitle) 0.dp else 24.dp, | ||
| ), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
isMultiLineTitle 판별이 명시적 \n에만 의존합니다.
title.contains('\n')으로만 멀티라인 여부를 판단하므로, 개행 문자 없이 화면 폭 때문에 자동으로 두 줄이 되는 제목에서는 headerHeight=82.dp, BottomStart 정렬, bottom=24.dp 패딩이 그대로 적용됩니다. 이는 이 PR이 고치려는 문제(첫 줄이 시트 상단 모서리에 붙거나 잘림)를 그대로 재현할 수 있습니다.
현재 이 컴포넌트를 사용하는 곳들이 모두 의도적으로 \n을 명시("원하는 사진을\n선택해주세요")하고 있어 실사용상 문제가 드러나지 않았을 뿐, 공용 디자인시스템 컴포넌트로서는 "제목에 두 줄 이상이면 반드시 \n을 넣어야 한다"는 계약을 KDoc에 명시하거나, onTextLayout으로 실제 줄 수를 측정하는 방식으로 보강하는 것을 권장합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt`
around lines 124 - 143, Update the title layout logic around isMultiLineTitle so
titles that wrap naturally due to available width receive the same multi-line
height, alignment, and padding as explicitly newline-separated titles. Prefer
using Text’s onTextLayout to derive whether the rendered line count exceeds one,
while preserving the existing single-line behavior and avoiding reliance on
title.contains('\n') alone.
- ConfirmBottomSheet 하한만 보장 - 네이밍 컨벤션에 맞춰 수정
Gemini AI 코드리뷰안녕하세요! PR 잘 검토했습니다. 이번 PR은 프로필 이미지 피커 바텀시트와 관련 컴포넌트들을 디자인 시스템 레이어에 추가하는 작업이네요. 전반적으로 코드가 매우 깔끔하고, Jetpack Compose의 모범 사례와 디자인 시스템 원칙을 훌륭하게 준수하고 있어 인상 깊습니다. 특히 자세한 KDoc과 책임 경계 설명은 다른 개발자들이 컴포넌트를 이해하고 사용하기 쉽게 할 것입니다. 몇 가지 코멘트와 개선 제안 사항을 드립니다. 🟢
|
| /** | ||
| * 프로필 사진 선택 공통 바텀시트 | ||
| * | ||
| * 마이페이지·온보딩 등에서 동일 UI로 재사용합니다. | ||
| * [ConfirmBottomSheet] 셸 + [ProfileImagePickerContent](토글·캐러셀) 조합입니다. | ||
| * | ||
| * 모델·캐러셀·토글 구현은 | ||
| * `kr.co.call.designsystem.component.profileimage` 패키지에 있습니다. | ||
| * | ||
| * ## 책임 경계 | ||
| * | 이 컴포넌트 | 호출부 (feature / ViewModel) | | ||
| * |-------------|------------------------------| | ||
| * | 바텀시트 UI, 토글·캐러셀 표시 | 시트 열림 여부 | | ||
| * | 스와이프/탭에 따른 콜백 전달 | [images] 목록 (성별별 필터 포함) | | ||
| * | | [selectedGender], [selectedImageId] 보관 | | ||
| * | | 저장 API / 로컬 반영 | | ||
| * | ||
| * ## 호출 계약 | ||
| * 1. [images]는 **현재 성별에 해당하는 목록만** 넘깁니다. 성별 전환 시 목록을 갈아끼우세요. | ||
| * 2. [onGenderChange]에서 성별 state를 바꾼 뒤, 새 목록의 첫 항목 등으로 | ||
| * [selectedImageId]를 리셋하는 것을 권장합니다. | ||
| * 3. [onImageSelected]는 페이지 확정마다 호출됩니다. 저장 전 선택 반영에 사용합니다. | ||
| * 4. designsystem에 domain 모델·Repository를 넣지 마세요. [ProfileImageOption]으로 매핑합니다. | ||
| * | ||
| * - 사용 예시 | ||
| * ```kotlin | ||
| * import kr.co.call.designsystem.component.bottomsheet.ProfileImagePickerBottomSheet | ||
| * import kr.co.call.designsystem.component.profileimage.ProfileImageGender | ||
| * import kr.co.call.designsystem.component.profileimage.ProfileImageOption | ||
| * | ||
| * if (state.showImagePicker) { | ||
| * ProfileImagePickerBottomSheet( | ||
| * images = state.pickerImages, | ||
| * selectedGender = state.pickerGender, | ||
| * selectedImageId = state.selectedImageId, | ||
| * onGenderChange = { viewModel.onPickerGenderChange(it) }, | ||
| * onImageSelected = { viewModel.onPickerImageSelected(it) }, | ||
| * onSaveClick = { viewModel.onSaveProfileImage() }, | ||
| * onDismissRequest = { viewModel.dismissImagePicker() }, | ||
| * ) | ||
| * } | ||
| * ``` | ||
| * | ||
| * @param images 현재 성별 후보 목록 (빈 목록이면 empty 문구 표시) | ||
| * @param selectedGender 성별 토글 선택값 | ||
| * @param selectedImageId 현재 선택 이미지 ID. null이면 캐러셀 첫 페이지 | ||
| * @param onGenderChange 성별 변경. 목록 교체는 호출부에서 수행 | ||
| * @param onImageSelected 캐러셀 페이지 확정 시 | ||
| * @param onSaveClick 하단 저장 버튼 | ||
| * @param onDismissRequest 닫기 / 스크림 / 시스템 뒤로가기 | ||
| * @param title 시트 제목 (기본 2줄) | ||
| * @param confirmText 확인 버튼 문구 | ||
| */ | ||
| @Composable |
📄 작업 내용 요약
📎 Issue 번호
✅ 작업 목록
📝 기타 참고사항
Screen_recording_20260729_091840.webm
Summary by CodeRabbit
새 기능
개선