Skip to content

feat: 공통 바텀시트 및 휠피커 구현#30

Merged
codebidoof merged 6 commits into
developfrom
feat/#7-home-ui
Jul 24, 2026
Merged

feat: 공통 바텀시트 및 휠피커 구현#30
codebidoof merged 6 commits into
developfrom
feat/#7-home-ui

Conversation

@JiwonLee42

@JiwonLee42 JiwonLee42 commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

📄 작업 내용 요약

제네릭 WheelPicker 컴포넌트

  • 중앙 항목 계산
  • 스냅 스크롤
  • 선택 위치별 색상, 크기 처리
  • 스크롤 종료 후 선택값 전달

ConfirmBottomSheet 컴포넌트 - 공통 바텀시트

  • 공통 제목 및 닫기 버튼
  • 공통 확인 버튼
  • 387dp 고정 높이

사용 예시 ) 휠 피커 개수만큼 선언해서 바텀시트에 추가해서 사용하시면 됩니다!

2026-07-22.3.33.08.mov

📎 Issue 번호

  • closed #

✅ 작업 목록

  • 기능 구현
  • 코드 리뷰 반영
  • 테스트 코드 작성
  • 문서 업데이트

📝 기타 참고사항

Summary by CodeRabbit

Summary

  • 새로운 기능
    • 제목·본문·확인 버튼을 갖춘 공통 확인 바텀시트를 추가했습니다.
    • 날짜/시간을 휠로 선택하는 변경 바텀시트와 “메인 연인 교체하기” 다이얼로그를 추가했습니다.
    • 가운데 항목을 강조하며 스크롤로 선택하는 휠 피커를 도입했습니다.
    • 원형 닫기 아이콘을 추가했습니다.
  • 개선/변경
    • 일부 팝업의 닫기 동작 설정 방식을 필수 콜백 형태로 조정했습니다.

@JiwonLee42 JiwonLee42 self-assigned this Jul 21, 2026
@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

안녕하세요, @[PR Author's GitHub Username]님!

이번 PR을 통해 ConfirmBottomSheetWheelPicker 컴포넌트를 추가해주셔서 감사합니다. 두 컴포넌트 모두 디자인 시스템 모듈에 매우 적합한, 잘 설계된 제네릭 UI 컴포넌트이며, 특히 문서화가 훌륭합니다.

다음은 코드 리뷰 내용입니다.

전반적인 피드백

  • KDoc 품질: 두 컴포넌트 모두 KDoc이 정말 훌륭합니다! 명확한 설명, 사용 예시, 그리고 State hoisting에 대한 자세한 정보까지 제공되어 다른 개발자들이 사용하기 매우 편리할 것 같습니다. 이 부분은 정말 잘하셨습니다!
  • 컴포넌트 설계: 두 컴포넌트 모두 Jetpack Compose의 모범 사례, 특히 State hoisting 및 재사용성 원칙을 잘 따르고 있습니다. 제네릭하고 유연하게 설계되어 활용도가 높을 것 같습니다.

세부 피드백 및 제안 사항

ConfirmBottomSheet.kt

  1. 하드코딩된 문자열:

    • confirmText의 기본값 "확인" (62행)과 닫기 버튼 ImagecontentDescription "닫기" (104행)가 하드코딩된 문자열입니다.
    • 제안: 국제화(i18n) 및 일관성을 위해 strings.xml 리소스로 이동하는 것이 좋습니다.
      <!-- In strings.xml -->
      <string name="button_confirm">확인</string>
      <string name="content_description_close">닫기</string>
      // ConfirmBottomSheet.kt 파일 내 변경 예시
      confirmText: String = stringResource(R.string.button_confirm),
      // ...
      contentDescription = stringResource(R.string.content_description_close),
  2. 매직 넘버 (Dimension):

    • 여러 dp 값들이 하드코딩되어 있습니다. 이 값들은 디자인 시스템의 토큰(design token)이거나 CallTheme.dimens와 같은 곳에 명명된 상수로 정의되어야 합니다.
    • 예시: RoundedCornerShape40.dp, 고정 높이 387.dp, 82.dp, 16.dp, 24.dp, 5.dp, 48.dp, 27.dp, 29.dp 등 패딩, 크기, 높이 관련 값들.
    • 제안: 디자인 시스템 내에 상수들을 생성하여 관리하는 것을 권장합니다. 예를 들어:
      // CallTheme/Dimens.kt (예시)
      object CallDimens {
          val bottomSheetCornerRadius = 40.dp
          val confirmBottomSheetHeight = 387.dp
          val bottomSheetTitleHeight = 82.dp
          val paddingSmall = 5.dp
          val paddingMedium = 16.dp
          val paddingLarge = 24.dp
          val paddingXLarge = 29.dp
          val iconSizeDefault = 27.dp
          val touchTargetSize = 48.dp // IconButton에 사용
          // ... 필요한 만큼 추가
      }
      
      // 사용 예시:
      shape = RoundedCornerShape(topStart = CallDimens.bottomSheetCornerRadius, topEnd = CallDimens.bottomSheetCornerRadius),
      modifier = Modifier.height(CallDimens.confirmBottomSheetHeight),
  3. Scrim 색상 Alpha 값:

    • Color.Black.copy(alpha = 0.2f) (72행)에서 알파 값이 매직 넘버로 사용되었습니다.
    • 제안: 이 알파 값(또는 전체 스크림 색상)이 공통 디자인 선택사항이라면 CallTheme.colors 내에 디자인 토큰으로 정의하는 것이 좋습니다.

WheelPicker.kt

  1. 매직 넘버 (Dimension 및 애니메이션):

    • itemHeight: Dp = 39.dpvisibleItemCount: Int = 5가 기본값 매직 넘버로 사용되었습니다.
    • tween(durationMillis = 180) 또한 애니메이션 지속 시간에 매직 넘버를 사용하고 있습니다.
    • 제안: 39.dp5CallDimens 또는 적절한 디자인 시스템 상수로 추출해주세요. 180 또한 애니메이션 지속 시간이 디자인 시스템의 모션 가이드라인에 속한다면 명명된 상수로 정의하는 것이 좋습니다.
  2. 예외 메시지 문자열:

    • require 함수의 메시지 "visibleItemCount must be a positive odd number." (80행)가 하드코딩된 문자열입니다.
    • 제안: 이 또한 strings.xml로 외부화하는 것이 좋습니다.

종합

위 제안들은 컴포넌트의 견고함, 유지보수성, 그리고 국제화 기능을 향상시킬 수 있는 사소한 개선점들입니다. 핵심적인 구현과 디자인 선택은 훌륭하게 잘 되어 있습니다.

위에 언급된 사항들을 반영해주시면 다시 한번 검토해 드리겠습니다!

수고하셨습니다!

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

디자인 시스템에 확인용 바텀시트와 제네릭 휠 피커를 추가하고 팝업 콜백 계약을 변경했습니다. 홈 기능에는 날짜·시간 변경 바텀시트와 캐릭터 변경 다이얼로그를 추가했으며, 관련 아이콘과 프리뷰를 포함했습니다.

Changes

디자인 시스템 컴포넌트

Layer / File(s) Summary
확인용 바텀시트 구현
core/designsystem/.../bottomsheet/ConfirmBottomSheet.kt, core/designsystem/src/main/res/drawable/ic_close_circle.xml
모달 설정, 제목, 본문 슬롯, 닫기 아이콘, 확인 버튼을 포함한 ConfirmBottomSheet와 프리뷰를 추가했습니다.
휠 피커 선택 및 렌더링
core/designsystem/.../picker/WheelPicker.kt
중앙 항목 계산, 스냅 스크롤, 스크롤 종료 콜백, 항목 크기 보간, 그라데이션 틴트를 구현했습니다.
팝업 dismiss 계약 및 눌림 상태
core/designsystem/.../popup/OneButtonPopup.kt, core/designsystem/.../popup/TwoButtonPopup.kt, core/designsystem/.../button/PrimaryButton.kt
팝업의 onDismissRequest를 필수 인자로 변경하고, 취소 버튼의 눌림 색상과 프리뷰 호출을 갱신했습니다.

홈 선택 UI

Layer / File(s) Summary
시간 변경 휠 바텀시트
feature/home/impl/.../TimeChangeBottomSheet.kt
날짜 범위와 30분 단위 시간 목록을 두 휠에 표시하고 선택 콜백을 연결했습니다.
캐릭터 변경 다이얼로그
feature/home/impl/.../dialog/CharacterChangeDialog.kt
캐릭터 목록, 프로필 이미지, 이름, D+ 배지, 캐릭터 추가 버튼 및 클릭 콜백을 추가했습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Conventional Commits 형식을 따르며, 공통 바텀시트와 휠피커 추가라는 PR 변경 내용을 잘 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Ui 변경 시 스크린샷 첨부 확인 ✅ Passed 변경에 Compose UI 파일들이 포함되고, PR 본문에 user-attachments 미디어 링크가 있어 스크린샷/화면 녹화 첨부 요건을 충족합니다.
모듈 의존성 방향 검증 ✅ Passed 변경 파일에서 다른 feature impl/core.data 직접 import는 없고, core/domain의 android/androidx import도 발견되지 않았습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#7-home-ui

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt (1)

78-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

스크림 색상도 디자인 토큰을 사용하세요.

Color.Black 직접 사용은 테마 토큰 경로를 우회합니다. 공통 스크림 토큰을 사용하거나, 없다면 토큰으로 정의해 테마 변경 시 함께 반영되게 하세요. As per path instructions, Theme/Color/Typography는 하드코딩 값 대신 디자인 토큰을 참조하는지.

🤖 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`
at line 78, Update the scrimColor assignment in ConfirmBottomSheet to use the
shared design-system scrim color token instead of Color.Black, preserving the
0.2f alpha unless the token already defines the intended opacity. If no shared
scrim token exists, add and reference one through the theme color-token path so
theme changes propagate consistently.

Source: Path instructions

🤖 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 101-143: Update the ConfirmBottomSheet content area around
content() so it occupies the remaining height within the fixed 387.dp Column by
placing it in a Modifier.weight(1f) container before the Spacer and
PrimaryButton. Allow the body content to scroll internally when it exceeds the
allocated space, while keeping the existing button visible and unchanged.

In
`@core/designsystem/src/main/java/kr/co/call/designsystem/component/picker/WheelPicker.kt`:
- Around line 119-129: Update the LaunchedEffect flow in WheelPicker to skip the
initial snapshotFlow emission before filtering for !isScrolling, adding the
required drop operator import. Preserve callback delivery only after an actual
scroll transition has stopped.

---

Nitpick comments:
In
`@core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt`:
- Line 78: Update the scrimColor assignment in ConfirmBottomSheet to use the
shared design-system scrim color token instead of Color.Black, preserving the
0.2f alpha unless the token already defines the intended opacity. If no shared
scrim token exists, add and reference one through the theme color-token path so
theme changes propagate consistently.
🪄 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: c7288279-7b8d-4348-8fd1-a2b3c647b8f3

📥 Commits

Reviewing files that changed from the base of the PR and between 8f30c10 and 079e417.

📒 Files selected for processing (3)
  • core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt
  • core/designsystem/src/main/java/kr/co/call/designsystem/component/picker/WheelPicker.kt
  • core/designsystem/src/main/res/drawable/ic_close_circle.xml

Comment on lines +101 to +143
Column(
modifier = Modifier
.fillMaxWidth()
.height(387.dp),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(82.dp),
) {
Text(
text = title,
modifier = Modifier
.align(Alignment.BottomStart)
.padding(start = 16.dp, bottom = 24.dp),
style = CallTheme.typography.bodyLargeBold,
color = CallTheme.colors.black,
)

IconButton(
onClick = onDismissRequest,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(top = 5.dp, end = 5.dp)
.size(48.dp),
) {
Image(
painter = painterResource(R.drawable.ic_close_circle),
contentDescription = "닫기",
modifier = Modifier.size(27.dp),
)
}
}

content()

Spacer(modifier = Modifier.height(24.dp))

PrimaryButton(
text = confirmText,
onClick = onConfirmClick,
modifier = Modifier.padding(horizontal = 29.dp),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target file and inspect the relevant range with line numbers.
target="core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt"
wc -l "$target"
sed -n '1,220p' "$target" | cat -n

# Find similar bottom sheet implementations for comparison.
fd -a 'BottomSheet.kt' core/designsystem/src/main/java/kr/co/call/designsystem/component | sed -n '1,120p'
rg -n "content\(\)|weight\(1f\)|height\([0-9]+\.dp\)|CallTheme\.colors\.black|scrim" core/designsystem/src/main/java/kr/co/call/designsystem/component -S

Repository: call-from-ai/CallFromAi_Android

Length of output: 8990


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file's imports and structure more precisely.
target="core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt"
ast-grep outline "$target" --view expanded

Repository: call-from-ai/CallFromAi_Android

Length of output: 399


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Probe whether the repo contains design token usage and bottom sheet layout patterns.
rg -n "CallTheme\.colors\.black|Color\.Black|scrimColor|weight\(1f\)|verticalScroll|LazyColumn|BottomSheet" core/designsystem/src/main/java -S

Repository: call-from-ai/CallFromAi_Android

Length of output: 3341


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read only the exact lines around the comment snippet after mapping the file.
target="core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt"
sed -n '80,170p' "$target" | cat -n

Repository: call-from-ai/CallFromAi_Android

Length of output: 3127


🏁 Script executed:

#!/bin/bash
set -euo pipefail
target="core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt"
python3 - <<'PY'
from pathlib import Path
p = Path("core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt")
for i, line in enumerate(p.read_text().splitlines(), 1):
    if 1 <= i <= 220:
        print(f"{i:4d}: {line}")
PY

Repository: call-from-ai/CallFromAi_Android

Length of output: 6415


본문 슬롯에 남은 높이를 먼저 할당하세요. ConfirmBottomSheet.kt:101-143에서 content()가 고정 높이(387dp) 안에서 버튼보다 먼저 무제한으로 측정되어, 본문이 길면 확인 버튼이 시트 밖으로 밀릴 수 있습니다. content()Modifier.weight(1f) 영역에 넣고, 필요하면 내부 스크롤을 허용하세요.

🤖 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 101 - 143, Update the ConfirmBottomSheet content area around
content() so it occupies the remaining height within the fixed 387.dp Column by
placing it in a Modifier.weight(1f) container before the Spacer and
PrimaryButton. Allow the body content to scroll internally when it exceeds the
allocated space, while keeping the existing button visible and unchanged.

Comment on lines +119 to +129
// 스크롤이 끝난 시점의 선택값만 외부에 전달
LaunchedEffect(listState, items) {
snapshotFlow { listState.isScrollInProgress }
.distinctUntilChanged()
.filter { isScrolling -> !isScrolling }
.collect {
items.getOrNull(selectedIndex)?.let { selectedItem ->
currentOnItemSelected(selectedIndex, selectedItem)
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

마운트 시점에 onItemSelected가 즉시 호출됩니다.

listState.isScrollInProgress의 초기값은 false이므로, snapshotFlow의 첫 방출값이 distinctUntilChanged()를 그대로 통과해 filter { !isScrolling }에 걸려 컴포저블이 마운트되자마자 onItemSelected가 호출됩니다. KDoc에는 "스크롤이 멈춘 뒤에만 호출되어 ViewModel에 과도한 이벤트가 전달되지 않습니다"라고 명시되어 있는데, 실제로는 사용자가 스크롤을 하지 않아도 초기 선택값에 대해 콜백이 한 번 더 발생합니다. 콜백이 네트워크 호출/상태 갱신 등 부수효과를 유발하는 호출부가 있다면 의도치 않은 초기 실행이 발생할 수 있습니다.

초기 방출을 건너뛰도록 수정하는 것을 제안합니다.

🐛 제안하는 수정
     LaunchedEffect(listState, items) {
         snapshotFlow { listState.isScrollInProgress }
             .distinctUntilChanged()
+            .drop(1) // 마운트 시점의 초기 상태(false) 방출은 건너뜀
             .filter { isScrolling -> !isScrolling }
             .collect {
                 items.getOrNull(selectedIndex)?.let { selectedItem ->
                     currentOnItemSelected(selectedIndex, selectedItem)
                 }
             }
     }

kotlinx.coroutines.flow.drop import 추가가 필요합니다.

📝 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
// 스크롤이 끝난 시점의 선택값만 외부에 전달
LaunchedEffect(listState, items) {
snapshotFlow { listState.isScrollInProgress }
.distinctUntilChanged()
.filter { isScrolling -> !isScrolling }
.collect {
items.getOrNull(selectedIndex)?.let { selectedItem ->
currentOnItemSelected(selectedIndex, selectedItem)
}
}
}
// 스크롤이 끝난 시점의 선택값만 외부에 전달
LaunchedEffect(listState, items) {
snapshotFlow { listState.isScrollInProgress }
.distinctUntilChanged()
.drop(1) // 마운트 시점의 초기 상태(false) 방출은 건너뜀
.filter { isScrolling -> !isScrolling }
.collect {
items.getOrNull(selectedIndex)?.let { selectedItem ->
currentOnItemSelected(selectedIndex, selectedItem)
}
}
}
🤖 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/picker/WheelPicker.kt`
around lines 119 - 129, Update the LaunchedEffect flow in WheelPicker to skip
the initial snapshotFlow emission before filtering for !isScrolling, adding the
required drop operator import. Preserve callback delivery only after an actual
scroll transition has stopped.

@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

안녕하세요! PR 잘 봤습니다. 전반적으로 Jetpack Compose와 Kotlin의 고급 기능들을 잘 활용하고 계시네요. 특히 WheelPickerConfirmBottomSheet 컴포저블의 구조화 및 상태 관리가 인상 깊습니다. 몇 가지 개선점을 중심으로 코드 리뷰를 진행하겠습니다.


💡 전반적인 피드백 (Overall Feedback)

전반적으로 Clean Architecture 원칙과 Jetpack Compose 모범 사례를 잘 따르고 계십니다. 특히 rememberUpdatedState, derivedStateOf, snapshotFlow를 활용한 WheelPicker의 스크롤 상태 관리는 매우 훌륭합니다. 다만, 디자인 시스템 컴포넌트임에도 불구하고 dp 값이나 Color와 같은 매직 넘버가 직접 하드코딩된 부분이 많습니다. 이 부분을 개선하면 더욱 견고하고 유지보수하기 좋은 디자인 시스템이 될 것 같습니다.


📝 상세 코드 리뷰 (Detailed Code Review)

1. core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt

  • Jetpack Compose (Rule 2)
    • 매직 넘버 (Hardcoded values):
      • height(387.dp): 주석에 디자인 규격으로 고정된다고 명시되어 있지만, 이 값은 designsystem 모듈 내의 Dimens 객체 또는 유사한 상수 파일로 추출하여 관리하는 것이 좋습니다. 예를 들어 CallTheme.dimens.confirmBottomSheetHeight와 같이 사용하면 일관성을 유지하고 추후 디자인 변경 시 한 곳에서만 수정할 수 있습니다.
      • RoundedCornerShape(topStart = 40.dp, topEnd = 40.dp): 마찬가지로 상수로 추출해주세요.
      • scrimColor = Color.Black.copy(alpha = 0.2f): 0.2f 또한 매직 넘버입니다. 테마의 스크림 알파 값으로 정의하거나 상수로 빼는 것을 권장합니다.
      • height(82.dp), padding(start = 16.dp, bottom = 24.dp), padding(top = 5.dp, end = 5.dp), size(48.dp), size(27.dp), Spacer(modifier = Modifier.height(24.dp)), padding(horizontal = 29.dp) 등 모든 dp 값들을 Dimens 객체로 추출하여 관리하면 좋습니다.
    • Composables & Recomposition: rememberModalBottomSheetStatecontent 슬롯을 사용한 점, 제목과 버튼 로직을 분리한 ConfirmBottomSheetContent 컴포저블 분리는 아주 좋습니다. 불필요한 리컴포지션을 유발하지 않고, 재사용성을 높이는 좋은 구조입니다.
    • Side Effect: 바텀시트 자체의 로직이므로 LaunchedEffectDisposableEffect가 필요 없습니다. 적절합니다.

2. core/designsystem/src/main/java/kr/co/call/designsystem/component/picker/WheelPicker.kt

  • Kotlin (Rule 1)
    • !! 사용 지양: items.getOrNull(selectedIndex)?.let { ... } 을 사용하여 null 안전성을 잘 확보했습니다. 훌륭합니다.
    • 매직 넘버 (Hardcoded values):
      • itemHeight: Dp = 39.dp, visibleItemCount: Int = 5: 디폴트 값으로 제공하는 것은 좋지만, 이 값들도 Dimens 객체의 상수로 정의하여 사용하는 것이 좋습니다.
      • animationSpec = tween(durationMillis = 180): 180ms는 애니메이션 지속시간을 나타내는 매직 넘버입니다. AnimationConstants.ShortDurationMillis와 같은 상수로 정의하거나 디자인 시스템 내에서 애니메이션 관련 상수를 관리하는 것이 좋습니다.
      • Spring.DampingRatioNoBouncy, Spring.StiffnessMediumLow: 이 값들은 Compose Animation의 기본 상수이므로 괜찮습니다.
  • Jetpack Compose (Rule 2)
    • Recomposition:
      • selectedIndex by remember(listState) { derivedStateOf { ... } }: derivedStateOf를 사용하여 selectedIndex가 실제로 변경될 때만 리컴포지션을 유발하도록 한 점이 매우 좋습니다. listState의 모든 변화에 반응하는 것을 피하면서 효율적인 UI 업데이트를 가능하게 합니다.
      • currentOnItemSelected by rememberUpdatedState(onItemSelected): LaunchedEffect 내부에서 최신 콜백을 참조하도록 rememberUpdatedState를 사용한 것은 LaunchedEffect의 모범 사례를 따르는 훌륭한 패턴입니다.
      • animateColorAsState, animateFloatAsState: 애니메이션을 통해 색상과 폰트 크기 변경이 발생할 때 해당 Text 컴포저블만 효율적으로 리컴포지션 되도록 잘 설계되었습니다.
    • State hoisting: listStateonItemSelected를 파라미터로 받아 호출부가 상태를 소유하도록 한 것은 State hoisting 원칙을 완벽하게 준수합니다.
    • Side Effect (LaunchedEffect):
      • snapshotFlow { listState.isScrollInProgress }.distinctUntilChanged().filter { !isScrolling }.collect { ... }: 스크롤이 완전히 멈춘 후에만 onItemSelected 콜백을 호출하도록 구현한 부분이 매우 좋습니다. ViewModel이나 상위 컴포넌트에 과도한 이벤트가 전달되는 것을 방지합니다.
    • require(visibleItemCount > 0 && visibleItemCount % 2 == 1): 유효성 검사 로직이 명확하고 적절합니다.
    • Preview: rememberLazyListState를 사용하여 초기 상태를 지정하고 프리뷰하는 방식이 좋습니다.
    • 문서화: 컴포넌트에 대한 주석이 매우 상세하고 명확하여 사용법과 의도를 이해하기 쉽습니다.

3. core/designsystem/src/main/java/kr/co/call/designsystem/component/popup/OneButtonPopup.kt & TwoButtonPopup.kt

  • Kotlin (Rule 1)
    • onDismissRequest: () -> Unit = {} 변경: onDismissRequest 파라미터의 기본값을 삭제하여 호출하는 쪽에서 명시적으로 onDismissRequest를 처리하도록 강제한 변경은 매우 바람직합니다. Dialog는 항상 닫기 로직을 명확히 정의해야 합니다. (이 변경은 breaking change이므로, PR 설명에 명시하거나 팀원들에게 공유하는 것이 좋습니다.)
  • Jetpack Compose (Rule 2)
    • 매직 넘버 (Hardcoded values):
      • elevation = 15.dp, ambientColor = Color.Black.copy(alpha = 0.08f), spotColor = Color.Black.copy(alpha = 0.08f): 그림자 관련 값들도 DimensTheme 객체에서 상수로 관리하는 것이 좋습니다.
      • width(321.dp), RoundedCornerShape(30.dp) 등 팝업의 크기나 모양 관련 dp 값들도 상수로 추출해주세요.
    • TwoButtonPopup trailing comma: pressedContainerColor = CallTheme.colors.gray400,와 같이 trailing comma를 추가한 것은 코드 포맷팅 및 diff 가독성에 좋은 습관입니다.

4. feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeDialog.kt

  • Kotlin (Rule 1)
    • null 안전성 (Null Safety):
      • character.imageUrl?.takeIf { it.isNotBlank() }: AsyncImagemodel에 URL을 전달할 때, null이거나 빈 문자열인 경우를 takeIf로 필터링한 후 defaultProfile을 사용하도록 한 점이 매우 훌륭합니다. !! 사용을 지양하고 안전하게 처리하는 모범 사례입니다.
    • 매직 넘버 (Hardcoded values):
      • 이 컴포넌트에서도 width(321.dp), elevation = 15.dp, alpha = 0.08f, RoundedCornerShape(30.dp), padding 값들 (26.dp, 25.dp, 13.dp, 20.dp 등)이 다수 하드코딩되어 있습니다. 디자인 시스템 모듈의 Dimens 객체 또는 피처 모듈 내의 전용 Dimens 객체를 만들어 관리하는 것을 강력히 권장합니다.
      • fontSize = 14.sp, lineHeight = 14.sp: + 버튼의 텍스트 크기도 CallTheme.typography 시스템을 따르거나 상수로 정의하는 것이 좋습니다.
  • Jetpack Compose (Rule 2)
    • Recomposition:
      • MutableInteractionSourcecollectIsPressedAsState()를 사용하여 버튼의 눌림 상태를 추적하고 UI를 업데이트하는 방식이 올바릅니다. 불필요한 리컴포지션 없이 효율적으로 UI를 그릴 수 있습니다.
    • State hoisting: characters 리스트, onCharacterClick, onAddCharacterClick, onDismissRequest 모두 상위 컴포넌트에서 관리하도록 잘 호이스팅되었습니다.
    • UI 상태 (UI State): CharacterOptionUiModel이라는 UI 전용 데이터 클래스를 사용하여 UI 상태를 표현한 점이 좋습니다.
    • Accessibility: contentDescription = "${character.name} 프로필 이미지" 처럼 이미지에 대한 상세한 contentDescription을 제공한 점은 접근성을 높이는 좋은 사례입니다.
    • Coil 이미지 로딩: AsyncImageplaceholder, error, fallback을 모두 제공하여 다양한 상황에 대응하는 견고한 이미지 로딩 로직을 구현했습니다.
    • CharacterOption 컴포저블 분리: 리스트 아이템을 별도의 컴포저블로 분리하여 재사용성과 가독성을 높였습니다.

🚀 개선 제안 (Suggestions for Improvement)

  1. 매직 넘버 추출 (Extract Magic Numbers):

    • core/designsystem 모듈 내에 Dimens.kt 또는 DesignConstants.kt와 같은 파일을 생성하여 모든 dp 값, alpha 값, 애니메이션 지속 시간 등의 상수를 관리해주세요.
    • 예시:
      // core/designsystem/src/main/java/kr/co/call/designsystem/theme/Dimens.kt (새 파일)
      package kr.co.call.designsystem.theme
      
      import androidx.compose.ui.unit.dp
      
      object Dimens {
          val bottomSheetCornerRadius = 40.dp
          val bottomSheetHeight = 387.dp
          val buttonHorizontalPadding = 29.dp
          val buttonVerticalPadding = 24.dp // 예시
          val scrimAlpha = 0.2f
          val dialogElevation = 15.dp
          val dialogCornerRadius = 30.dp
          val characterDialogWidth = 321.dp
          val characterOptionItemHeight = 82.dp
          val characterProfileImageSize = 60.dp // 또는 (57.dp, 60.dp)
          // ... 기타 dp 값들
      }
      
      // 사용 예시:
      // shape = RoundedCornerShape(topStart = Dimens.bottomSheetCornerRadius, topEnd = Dimens.bottomSheetCornerRadius),
      // modifier = Modifier.height(Dimens.bottomSheetHeight),
      // scrimColor = Color.Black.copy(alpha = Dimens.scrimAlpha),
    • 이렇게 하면 디자인 가이드라인을 코드로 더 명확하게 표현하고, 추후 디자인 변경 시 유지보수 비용을 크게 줄일 수 있습니다.
  2. Typography 시스템 활용 극대화:

    • CharacterChangeDialog+ 버튼 텍스트처럼 fontSize = 14.sp와 같이 직접 sp를 사용하는 대신, CallTheme.typography에 적합한 TextStyle을 추가하거나 기존 스타일을 활용하는 것을 고려해주세요.
    • 예시: CallTheme.typography.captionMedium.copy(fontSize = 14.sp) 또는 아예 새로운 captionSmallBold 같은 스타일을 정의하는 방법이 있습니다.
  3. Hilt DI, Repository, ViewModel, FCM 관련 규칙:

    • 이번 PR은 UI/디자인 시스템 컴포넌트 위주라 해당 규칙들에 직접적으로 위배되는 사항은 발견되지 않았습니다. 다른 레이어의 코드에서도 현재와 같은 모범 사례를 유지해 주시면 좋을 것 같습니다.

✅ 결론 (Conclusion)

전반적으로 매우 좋은 코드입니다. Jetpack Compose의 상태 관리 및 성능 최적화 패턴을 잘 이해하고 적용하고 계십니다. 매직 넘버를 디자인 시스템 내의 상수로 추출하는 작업만 진행하면 더욱 완벽해질 것 같습니다. 이 부분만 개선되면 APPROVE 가능합니다. 수고 많으셨습니다!

@WAcAW9 WAcAW9 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

으아니 세상에나 wheelpicker 너무 감사드립니다,,,최고,,🫶
유용하게 잘 쓸게요! 고생하셨어요오

Comment on lines +38 to +45
/**
* 목록을 세로로 스크롤해 가운데 항목을 선택하는 공통 휠 피커입니다.
*
* 이 컴포넌트는 날짜나 시간 같은 값의 의미를 알지 않습니다. 호출부가 [items]와
* [itemText]를 제공하면 스크롤, 중앙 항목 계산, 스냅, 선택 색상 변경을 담당합니다.
* 따라서 화면마다 필요한 휠 개수가 다르면 이 컴포넌트를 하나 이상 조합해서 사용합니다.
*
* 날짜 한 개를 선택하는 예시:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks to 친절한 주석✨

@github-actions

Copy link
Copy Markdown

Gemini AI 코드리뷰

안녕하세요, 시니어 Android 개발자입니다. 이번 PR에서 새롭게 추가된 디자인 시스템 컴포넌트들과 홈 기능 관련 컴포넌트들을 리뷰했습니다. 전반적으로 Jetpack Compose의 모범 사례들을 잘 적용하고 계신 점이 인상 깊습니다. 특히 WheelPicker 컴포넌트의 rememberUpdatedStatesnapshotFlow를 활용한 이벤트 처리 방식은 매우 훌륭합니다.

다만, 몇 가지 개선할 점들이 있어 피드백을 드립니다.


1. core/designsystem/src/main/java/kr/co/call/designsystem/component/bottomsheet/ConfirmBottomSheet.kt

  • 하드코딩된 문자열/매직 넘버 개선:
    • 높이 고정 (387dp): 주석에서 디자인 규격상 고정 높이임을 명시했지만, height(387.dp)와 같이 직접적인 dp 값을 사용하는 것은 디자인 시스템의 유연성을 떨어뜨릴 수 있습니다. 만약 이 높이가 앱 전체에서 일관되게 사용되는 고정 값이라면 CallTheme.dimens.confirmBottomSheetHeight와 같은 형태로 상수화하여 관리하는 것이 좋습니다.
    • CornerShape/ScrimColor: RoundedCornerShape(topStart = 40.dp, topEnd = 40.dp)Color.Black.copy(alpha = 0.2f)는 매직 넘버입니다. CallTheme.shapes.bottomSheetCornerCallTheme.colors.scrim 등으로 정의하여 디자인 시스템 내에서 관리해야 합니다.
    • 내부 간격/크기: height(82.dp), padding(start = 16.dp, bottom = 24.dp), size(48.dp), size(27.dp), Spacer(height = 24.dp), padding(horizontal = 29.dp) 등 대부분의 dp 값들이 하드코딩되어 있습니다. 이 값들은 CallTheme.dimens에 적절한 이름(예: paddingLarge, iconSizeMedium)으로 정의하여 사용하면 일관성을 유지하고 유지보수를 용이하게 할 수 있습니다.
    • contentDescription: contentDescription = "닫기"는 하드코딩된 문자열입니다. @string/cd_close_button과 같이 스트링 리소스로 관리하여 다국어 지원에 대비해야 합니다.
    • Preview 문자열: Preview 내 title = "시간 변경", confirmText = "확인"은 Preview 용이므로 현재는 괜찮습니다.

2. core/designsystem/src/main/java/kr/co/call/designsystem/component/button/PrimaryButton.kt

  • Kotlin 코드 리뷰: Preview 코드에 쉼표가 추가된 것은 좋은 서식 개선입니다.

3. core/designsystem/src/main/java/kr/co/call/designsystem/component/picker/WheelPicker.kt

  • Jetpack Compose 코드 리뷰:
    • Recomposition/State Hoisting/Side Effect: rememberSnapFlingBehavior, derivedStateOf, rememberUpdatedState, LaunchedEffectsnapshotFlow를 활용하여 스크롤 상태 변화를 효율적으로 감지하고 불필요한 이벤트 호출을 방지하는 방식은 매우 훌륭합니다. Compose 모범 사례를 잘 따르고 있습니다.
    • null 안전성: items.getOrNull(selectedIndex)?.let { ... }과 같은 null 안전한 코드 작성은 매우 좋습니다.
  • 하드코딩된 문자열/매직 넘버 개선:
    • 기본값: itemHeight: Dp = 39.dp, visibleItemCount: Int = 5는 기본값으로 사용되지만, 이 역시 CallTheme.dimens 또는 별도의 상수 파일에서 관리하는 것이 좋습니다.
    • graphicsLayer(alpha = 0.99f): 0.99f라는 매직 넘버가 사용되었습니다. 특정 그래픽 레이어 문제를 해결하기 위한 일반적인 꼼수(workaround)로 보이지만, 주석으로 그 의도를 명확히 설명해주시면 좋을 것 같습니다 (예: "Forces a new graphics layer for blendMode to work correctly on some platforms.").

4. core/designsystem/src/main/java/kr/co/call/designsystem/component/popup/OneButtonPopup.kt & core/designsystem/src/main/java/kr/co/call/designsystem/component/popup/TwoButtonPopup.kt

  • Jetpack Compose 코드 리뷰:
    • onDismissRequest 변경: onDismissRequest에 기본값을 제거하여 호출부에서 명시적으로 dismiss 로직을 처리하도록 강제한 것은 좋은 변경입니다. Dialog 컴포넌트는 onDismissRequest가 비어있으면 백 프레스나 바깥 터치에 반응하지 않으므로, 이 변경은 잠재적인 문제를 방지합니다.
    • Preview 업데이트: 변경된 파라미터에 맞춰 Preview 코드를 업데이트한 것도 적절합니다.

5. core/designsystem/src/main/res/drawable/ic_close_circle.xml

  • XML 리소스 리뷰:
    • 하드코딩된 색상: android:fillColor="#F0EDEE"android:strokeColor="#5B5658"는 하드코딩된 색상입니다. 디자인 시스템 모듈 내의 Drawable이므로, CallTheme.colors.backgroundVariant와 같이 테마에서 정의된 색상을 사용하거나, 최소한 colors.xml에 명확한 의미를 가진 이름으로 정의하여 사용하는 것이 유지보수에 유리합니다.

6. feature/home/impl/src/main/java/kr/co/call/impl/component/TimeChangeBottomSheet.kt

  • Jetpack Compose 코드 리뷰:
    • Recomposition/State Hoisting: rememberrememberLazyListState를 사용하여 상태를 적절하게 관리하고 있습니다. DateTimeFormatterremember로 캐싱한 것도 좋습니다.
  • 하드코딩된 문자열/매직 넘버 개선:
    • 날짜/시간 범위: (-30L..90L) (날짜 범위) 및 List(48) (30분 단위 개수)와 같이 날짜/시간 생성 로직에 사용된 매직 넘버는 상수(예: DATE_RANGE_START_DAYS, TIME_SLOTS_PER_DAY)로 추출하는 것이 좋습니다.
    • 초기 인덱스: ?: 30, ?: 0과 같은 초기 인덱스 값도 상수로 정의하는 것이 좋습니다.
    • title: ConfirmBottomSheet에 전달되는 title = "시간 변경"은 하드코딩된 문자열입니다. @string/time_change_title과 같이 스트링 리소스로 관리해야 합니다.
    • 간격/크기: offset(y = (-22).dp), padding(horizontal = 35.dp), width(150.dp), width(120.dp) 등 대부분의 dp 값들이 하드코딩되어 있습니다. 이 값들도 디자인 시스템 테마(CallTheme.dimens)에 정의된 상수를 사용하는 것이 좋습니다. 특히 디바이더의 offsetitemHeight를 기반으로 계산되도록 하는 것이 더 견고할 수 있습니다.
    • 날짜 형식: "${date.monthValue}월 ${date.dayOfMonth}일"에서 "월", "일"과 같은 문자열은 하드코딩되어 있습니다. DateTimeFormatter를 사용하여 로케일과 지역화에 강건하게 처리하거나, 최소한 @string/format_month_day와 같은 스트링 리소스를 활용해야 합니다.

7. feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeDialog.kt

  • Jetpack Compose 코드 리뷰:
    • UI 상태를 data class로 표현: CharacterOptionUiModel을 사용하여 UI 상태를 잘 표현하고 있습니다. (해당 data class의 정의는 보이지 않지만, 사용 방식상 val로 구성된 data class로 보입니다.)
    • Recomposition: MutableInteractionSourcecollectIsPressedAsState()를 사용하여 UI 인터랙션에 따른 상태를 효율적으로 관리하고 있습니다.
  • 하드코딩된 문자열/매직 넘버 개선:
    • 전반적인 dp 값: width(321.dp), elevation = 15.dp, RoundedCornerShape(30.dp), padding(top = 26.dp, bottom = 25.dp), height(13.dp), height(20.dp), size(16.dp), fontSize = 14.sp, width(11.dp), height(82.dp), padding(start = 54.dp), size(width = 57.dp, height = 60.dp), width(17.dp), RoundedCornerShape(5.dp), padding(horizontal = 6.dp) 등 다이얼로그의 크기, 간격, 폰트 크기, 그림자 값 등 거의 모든 dpsp 값이 하드코딩되어 있습니다. 이 모든 값들을 CallTheme.dimens, CallTheme.shapes, CallTheme.elevations, CallTheme.typography와 같은 디자인 시스템의 표준 상수로 관리해야 합니다.
    • 하드코딩된 문자열: "메인 연인 교체하기", "아래 중에 교체하실\n연인을 선택해주세요.", "+", "캐릭터 추가하기", "D+ ${character.day}"와 같은 모든 사용자에게 노출되는 문자열은 @string 리소스로 관리되어야 합니다. 특히 + 기호도 텍스트로 처리되는 경우 다국어 지원 시 문제가 될 수 있습니다.
    • alpha 값: Color.Black.copy(alpha = 0.08f)와 같은 alpha 값도 디자인 시스템 색상(CallTheme.colors.shadowAlpha)의 일부로 관리해야 합니다.

총평

전반적으로 Jetpack Compose를 활용한 컴포넌트 개발에 대한 이해도가 높다고 생각합니다. 특히 UI 상태 관리 및 recomposition 최적화 측면에서 좋은 코드를 보여주고 있습니다. 하지만 디자인 시스템 모듈인 만큼, 매직 넘버와 하드코딩된 문자열을 디자인 시스템의 정의된 값으로 교체하여 일관성과 유지보수성을 확보하는 작업이 시급합니다. 이 부분을 개선한다면 더욱 견고하고 확장성 있는 디자인 시스템을 구축할 수 있을 것입니다.

다음 단계에서는 하드코딩된 값들을 추출하고 CallTheme에 통합하는 작업을 진행해주시면 좋겠습니다.

수고하셨습니다!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
feature/home/impl/src/main/java/kr/co/call/impl/component/TimeChangeBottomSheet.kt (1)

50-52: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

초기 인덱스 fallback을 목록 생성 로직에서 파생시키세요.

?: 30-30L..90L 범위에 종속된 매직 넘버로, 범위가 바뀌면 조용히 깨집니다. dates.indexOf(today)로 계산하면 더 안전합니다. 또한 selectedTime이 30분 단위 격자에 없을 경우(?: 0) 자정으로 튀는 대신 가장 가까운 슬롯으로 보정하는 편이 사용자 기대에 더 부합합니다.

♻️ 제안 수정
-    val initialDateIndex = dates.indexOf(selectedDate).takeIf { index -> index >= 0 }
-        ?: 30
-    val initialTimeIndex = times.indexOf(selectedTime).takeIf { index -> index >= 0 } ?: 0
+    val initialDateIndex = dates.indexOf(selectedDate).takeIf { index -> index >= 0 }
+        ?: dates.indexOf(today)
+    val initialTimeIndex = times.indexOf(selectedTime).takeIf { index -> index >= 0 }
+        ?: (selectedTime.toSecondOfDay() / 1800).coerceIn(0, times.lastIndex)
🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/TimeChangeBottomSheet.kt`
around lines 50 - 52, Update the initial index calculations in the bottom-sheet
setup: derive the date fallback from the generated dates list using today’s
index instead of the hardcoded 30, and when selectedTime is absent from the
30-minute slots, select the nearest available time slot rather than defaulting
to index 0. Preserve exact matches and ensure both fallbacks remain valid for
the generated lists.
feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeDialog.kt (1)

56-70: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

고정 너비(321dp)를 의도대로 적용하려면 usePlatformDefaultWidth = false를 명시하세요.

DialogProperties.usePlatformDefaultWidth의 기본값은 true라, 이 설정만 쓰면 플랫폼 기본 다이얼로그 폭(대부분 모바일 기준 약 320dp) 기준으로 크기 제약이 적용됩니다. 321.dp가 이를 약간 초과하지만 이 제약이 항상 적용되지 않도록 properties = DialogProperties(usePlatformDefaultWidth = false)로 명시하는 편이 의도한 레이아웃이 일관되게 나타납니다.

🐛 제안 수정
-    Dialog(onDismissRequest = onDismissRequest) {
+    Dialog(
+        onDismissRequest = onDismissRequest,
+        properties = DialogProperties(usePlatformDefaultWidth = false),
+    ) {
🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeDialog.kt`
around lines 56 - 70, Update the Dialog invocation in CharacterChangeDialog to
pass DialogProperties with usePlatformDefaultWidth set to false, ensuring the
Column’s explicit 321.dp width is applied consistently.
🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeDialog.kt`:
- Around line 58-69: Update the dialog modifier around the background in
CharacterChangeDialog to clip content to the same 30.dp RoundedCornerShape,
ensuring CharacterOption pressed-state backgrounds remain inside the rounded
corners while preserving the existing shadow and background styling.

---

Nitpick comments:
In
`@feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeDialog.kt`:
- Around line 56-70: Update the Dialog invocation in CharacterChangeDialog to
pass DialogProperties with usePlatformDefaultWidth set to false, ensuring the
Column’s explicit 321.dp width is applied consistently.

In
`@feature/home/impl/src/main/java/kr/co/call/impl/component/TimeChangeBottomSheet.kt`:
- Around line 50-52: Update the initial index calculations in the bottom-sheet
setup: derive the date fallback from the generated dates list using today’s
index instead of the hardcoded 30, and when selectedTime is absent from the
30-minute slots, select the nearest available time slot rather than defaulting
to index 0. Preserve exact matches and ensure both fallbacks remain valid for
the generated lists.
🪄 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: 81f39958-4d96-4053-865f-990bd889a5d2

📥 Commits

Reviewing files that changed from the base of the PR and between 079e417 and a1894f1.

📒 Files selected for processing (6)
  • core/designsystem/src/main/java/kr/co/call/designsystem/component/button/PrimaryButton.kt
  • core/designsystem/src/main/java/kr/co/call/designsystem/component/picker/WheelPicker.kt
  • core/designsystem/src/main/java/kr/co/call/designsystem/component/popup/OneButtonPopup.kt
  • core/designsystem/src/main/java/kr/co/call/designsystem/component/popup/TwoButtonPopup.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/TimeChangeBottomSheet.kt
  • feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeDialog.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/designsystem/src/main/java/kr/co/call/designsystem/component/picker/WheelPicker.kt

Comment on lines +58 to +69
modifier = modifier
.width(321.dp)
.shadow(
elevation = 15.dp,
shape = RoundedCornerShape(30.dp),
ambientColor = Color.Black.copy(alpha = 0.08f),
spotColor = Color.Black.copy(alpha = 0.08f),
)
.background(
color = CallTheme.colors.background,
shape = RoundedCornerShape(30.dp),
)

@coderabbitai coderabbitai Bot Jul 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

둥근 모서리 배경에 .clip이 없어 눌림 상태 배경이 모서리를 벗어날 수 있습니다.

Modifier.background(color, shape)는 지정한 도형 안에만 색을 채울 뿐, 자식 뷰의 드로잉을 잘라내지 않습니다. CharacterOption의 눌림 배경(사각형, 라인 149-166)이 첫/마지막 항목일 경우 다이얼로그의 둥근 모서리(30.dp) 밖으로 보일 수 있습니다.

🎨 제안 수정
             modifier = modifier
                 .width(321.dp)
                 .shadow(
                     elevation = 15.dp,
                     shape = RoundedCornerShape(30.dp),
                     ambientColor = Color.Black.copy(alpha = 0.08f),
                     spotColor = Color.Black.copy(alpha = 0.08f),
                 )
+                .clip(RoundedCornerShape(30.dp))
                 .background(
                     color = CallTheme.colors.background,
                     shape = RoundedCornerShape(30.dp),
                 )
📝 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
modifier = modifier
.width(321.dp)
.shadow(
elevation = 15.dp,
shape = RoundedCornerShape(30.dp),
ambientColor = Color.Black.copy(alpha = 0.08f),
spotColor = Color.Black.copy(alpha = 0.08f),
)
.background(
color = CallTheme.colors.background,
shape = RoundedCornerShape(30.dp),
)
modifier = modifier
.width(321.dp)
.shadow(
elevation = 15.dp,
shape = RoundedCornerShape(30.dp),
ambientColor = Color.Black.copy(alpha = 0.08f),
spotColor = Color.Black.copy(alpha = 0.08f),
)
.clip(RoundedCornerShape(30.dp))
.background(
color = CallTheme.colors.background,
shape = RoundedCornerShape(30.dp),
)
🤖 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
`@feature/home/impl/src/main/java/kr/co/call/impl/component/dialog/CharacterChangeDialog.kt`
around lines 58 - 69, Update the dialog modifier around the background in
CharacterChangeDialog to clip content to the same 30.dp RoundedCornerShape,
ensuring CharacterOption pressed-state backgrounds remain inside the rounded
corners while preserving the existing shadow and background styling.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

아이건 적용해야할듯?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@codebidoof 네, 적용하는 편이 좋습니다. .shadow(...) 다음에 동일한 RoundedCornerShape(30.dp).clip(...)을 추가하면 그림자는 유지하면서 항목의 눌림 배경이 다이얼로그 모서리 밖으로 렌더링되지 않습니다.

@codebidoof codebidoof left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

머지할게여~고생했어여

@codebidoof
codebidoof merged commit 78be0ba into develop Jul 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants