[Feature] 콜밴 글쓰기 참여 인원 및 제출 버튼 추가#1326
[Feature] 콜밴 글쓰기 참여 인원 및 제출 버튼 추가#1326JaeYoung290 wants to merge 6 commits intofeature/#1283-callvan-create-time-fieldfrom
Conversation
WalkthroughCallVan 생성 UI에 참여자 수 조정 섹션과 제출 하단 바 컴포넌트, 및 참여자 수 표시용 문자열 리소스가 추가되었습니다. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
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 |
73b4a7a to
766e48f
Compare
7eba53e to
463355a
Compare
| participantsText: String, | ||
| count: Int, | ||
| onDecrement: () -> Unit, | ||
| onIncrement: () -> Unit |
| style = KoinTheme.typography.medium16, | ||
| color = RebrandKoinTheme.colors.primary500 |
| .padding(horizontal = 16.dp, vertical = 8.dp) | ||
| ) | ||
| VerticalDivider( | ||
| modifier = Modifier.height(38.dp), |
| style = KoinTheme.typography.regular12, | ||
| color = KoinTheme.colors.neutral500 |
…1283-callvan-create-participants-and-submit
…1283-callvan-create-participants-and-submit
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
| fun CallvanParticipantsSection( | ||
| participantsText: String, | ||
| count: Int, | ||
| modifier: Modifier = Modifier, | ||
| onDecrement: () -> Unit = {}, | ||
| onIncrement: () -> Unit = {} |
There was a problem hiding this comment.
[Major] 스텝퍼 버튼의 활성화/비활성화 상태 표현 불가
현재 컴포넌트는 감소/증가 버튼의 활성화 상태를 외부에서 제어할 수 없습니다. 참여 인원이 최솟값(1명)이거나 최댓값(8명)일 때 해당 버튼을 비활성화하여 사용자에게 명확한 피드백을 주어야 합니다.
| fun CallvanParticipantsSection( | |
| participantsText: String, | |
| count: Int, | |
| modifier: Modifier = Modifier, | |
| onDecrement: () -> Unit = {}, | |
| onIncrement: () -> Unit = {} | |
| fun CallvanParticipantsSection( | |
| participantsText: String, | |
| count: Int, | |
| modifier: Modifier = Modifier, | |
| isDecrementEnabled: Boolean = true, | |
| isIncrementEnabled: Boolean = true, | |
| onDecrement: () -> Unit = {}, | |
| onIncrement: () -> Unit = {} |
이후 각 아이콘 tint 색상도 활성화 상태에 따라 분기:
tint = if (isDecrementEnabled) RebrandKoinTheme.colors.primary500 else RebrandKoinTheme.colors.neutral400호출 측(ViewModel/Screen)에서는 다음처럼 제어:
isDecrementEnabled = count > 1,
isIncrementEnabled = count < 8,| Icon( | ||
| imageVector = ImageVector.vectorResource(R.drawable.ic_remove), | ||
| contentDescription = null, | ||
| tint = RebrandKoinTheme.colors.primary500, | ||
| modifier = Modifier | ||
| .clickable(onClick = onDecrement) | ||
| .padding(horizontal = 8.dp, vertical = 7.dp) | ||
| .size(24.dp) | ||
| ) |
There was a problem hiding this comment.
[Major] 터치 타겟 크기 미달 및 접근성 누락
두 가지 문제가 있습니다:
-
터치 타겟 크기: 현재 유효 터치 영역은
40dp × 38dp입니다 (패딩 8+8=16dp, 7+7=14dp + 아이콘 24dp). Material Design 가이드라인에서는 최소48dp × 48dp를 요구합니다. 작은 타겟은 특히 모바일에서 오조작을 유발합니다. -
접근성:
contentDescription = null로 설정되어 있어 TalkBack 사용자가 이 버튼의 용도를 알 수 없습니다.
IconButton을 사용하면 두 문제를 모두 해결할 수 있습니다:
| Icon( | |
| imageVector = ImageVector.vectorResource(R.drawable.ic_remove), | |
| contentDescription = null, | |
| tint = RebrandKoinTheme.colors.primary500, | |
| modifier = Modifier | |
| .clickable(onClick = onDecrement) | |
| .padding(horizontal = 8.dp, vertical = 7.dp) | |
| .size(24.dp) | |
| ) | |
| IconButton( | |
| onClick = onDecrement, | |
| enabled = isDecrementEnabled, | |
| ) { | |
| Icon( | |
| imageVector = ImageVector.vectorResource(R.drawable.ic_remove), | |
| contentDescription = stringResource(R.string.callvan_create_decrement), | |
| tint = if (isDecrementEnabled) RebrandKoinTheme.colors.primary500 else RebrandKoinTheme.colors.neutral400, | |
| modifier = Modifier.size(24.dp) | |
| ) | |
| } |
IconButton은 기본적으로 48dp 터치 타겟을 보장합니다.
| Icon( | ||
| imageVector = Icons.Default.Add, | ||
| contentDescription = null, | ||
| tint = RebrandKoinTheme.colors.primary500, | ||
| modifier = Modifier | ||
| .clickable(onClick = onIncrement) | ||
| .padding(horizontal = 8.dp, vertical = 7.dp) | ||
| .size(24.dp) | ||
| ) |
There was a problem hiding this comment.
[Major] 터치 타겟 크기 미달 및 접근성 누락 (감소 버튼과 동일)
증가 버튼도 동일하게 IconButton으로 교체하고 contentDescription을 지정해야 합니다:
| Icon( | |
| imageVector = Icons.Default.Add, | |
| contentDescription = null, | |
| tint = RebrandKoinTheme.colors.primary500, | |
| modifier = Modifier | |
| .clickable(onClick = onIncrement) | |
| .padding(horizontal = 8.dp, vertical = 7.dp) | |
| .size(24.dp) | |
| ) | |
| IconButton( | |
| onClick = onIncrement, | |
| enabled = isIncrementEnabled, | |
| ) { | |
| Icon( | |
| imageVector = Icons.Default.Add, | |
| contentDescription = stringResource(R.string.callvan_create_increment), | |
| tint = if (isIncrementEnabled) RebrandKoinTheme.colors.primary500 else RebrandKoinTheme.colors.neutral400, | |
| modifier = Modifier.size(24.dp) | |
| ) | |
| } |
| Text( | ||
| text = participantsText, | ||
| style = RebrandKoinTheme.typography.regular14, | ||
| color = RebrandKoinTheme.colors.neutral800, | ||
| modifier = Modifier | ||
| .weight(1f) | ||
| .padding(horizontal = 16.dp, vertical = 8.dp) | ||
| ) |
There was a problem hiding this comment.
[Minor] participantsText와 count의 중복 데이터
현재 participantsText("4 명")와 count(4)는 동일한 인원 수를 두 개의 파라미터로 받고 있습니다. 호출 측에서 두 값이 항상 동기화되어야 하는 부담이 있으며, 불일치 버그가 생길 수 있습니다.
participantsText가 단순히 인원 수의 포맷된 표현이라면, 컴포넌트 내부에서 count를 기반으로 생성하는 것을 고려해 보세요:
// 컴포넌트 내부
val participantsText = stringResource(R.string.callvan_create_participants_format, count)
// strings.xml: <string name="callvan_create_participants_format">%d 명</string>만약 participantsText가 별도의 의미를 가진다면(예: 다른 포맷), 그 의도가 명확히 드러나도록 파라미터명이나 주석으로 구분을 명시해 주세요.
| @Preview(showBackground = true) | ||
| @Composable | ||
| private fun CallvanParticipantsSectionPreview() { | ||
| CallvanParticipantsSection( | ||
| participantsText = "4 명", | ||
| count = 4, | ||
| onDecrement = {}, | ||
| onIncrement = {} | ||
| ) | ||
| } |
There was a problem hiding this comment.
[Trivial] Preview에 테마 래퍼 누락
RebrandKoinTheme으로 감싸지 않으면 커스텀 색상(primary500, neutral400 등)과 타이포그래피가 Preview에서 올바르게 렌더링되지 않습니다.
| @Preview(showBackground = true) | |
| @Composable | |
| private fun CallvanParticipantsSectionPreview() { | |
| CallvanParticipantsSection( | |
| participantsText = "4 명", | |
| count = 4, | |
| onDecrement = {}, | |
| onIncrement = {} | |
| ) | |
| } | |
| @Preview(showBackground = true) | |
| @Composable | |
| private fun CallvanParticipantsSectionPreview() { | |
| RebrandKoinTheme { | |
| CallvanParticipantsSection( | |
| participantsText = "4 명", | |
| count = 4, | |
| onDecrement = {}, | |
| onIncrement = {} | |
| ) | |
| } | |
| } |
| imageVector = ImageVector.vectorResource(R.drawable.ic_remove), | ||
| contentDescription = null, | ||
| tint = RebrandKoinTheme.colors.primary500, | ||
| modifier = Modifier | ||
| .clickable(onClick = onDecrement) | ||
| .padding(horizontal = 8.dp, vertical = 7.dp) | ||
| .size(24.dp) | ||
| ) | ||
| Box( |
There was a problem hiding this comment.
[Major] 접근성: 클릭 가능한 아이콘에 contentDescription 누락
clickable이 적용된 Icon에 contentDescription = null을 사용하면 스크린 리더(TalkBack) 사용자가 해당 버튼의 역할을 알 수 없습니다. 접근성 가이드라인(WCAG 2.1 / Android Accessibility) 위반입니다.
또한 터치 타겟 크기가 24dp(icon) + 8dp*2(horizontal) + 7dp*2(vertical) = 40x38dp로, Android 권장 최소 터치 타겟 크기인 48x48dp 미만입니다.
| imageVector = ImageVector.vectorResource(R.drawable.ic_remove), | |
| contentDescription = null, | |
| tint = RebrandKoinTheme.colors.primary500, | |
| modifier = Modifier | |
| .clickable(onClick = onDecrement) | |
| .padding(horizontal = 8.dp, vertical = 7.dp) | |
| .size(24.dp) | |
| ) | |
| Box( | |
| Icon( | |
| imageVector = ImageVector.vectorResource(R.drawable.ic_remove), | |
| contentDescription = stringResource(R.string.callvan_create_participants_decrement), | |
| tint = RebrandKoinTheme.colors.primary500, | |
| modifier = Modifier | |
| .clickable(onClick = onDecrement) | |
| .padding(horizontal = 12.dp, vertical = 12.dp) | |
| .size(24.dp) | |
| ) |
strings.xml에
callvan_create_participants_decrement("인원 감소") 리소스 추가 필요
| imageVector = Icons.Default.Add, | ||
| contentDescription = null, | ||
| tint = RebrandKoinTheme.colors.primary500, | ||
| modifier = Modifier | ||
| .clickable(onClick = onIncrement) | ||
| .padding(horizontal = 8.dp, vertical = 7.dp) | ||
| .size(24.dp) | ||
| ) | ||
| } |
There was a problem hiding this comment.
[Major] 접근성: 증가 버튼도 contentDescription 누락
감소 버튼과 동일한 이슈입니다. 스크린 리더 사용자에게 이 버튼이 무엇을 하는지 설명이 필요합니다.
| imageVector = Icons.Default.Add, | |
| contentDescription = null, | |
| tint = RebrandKoinTheme.colors.primary500, | |
| modifier = Modifier | |
| .clickable(onClick = onIncrement) | |
| .padding(horizontal = 8.dp, vertical = 7.dp) | |
| .size(24.dp) | |
| ) | |
| } | |
| Icon( | |
| imageVector = Icons.Default.Add, | |
| contentDescription = stringResource(R.string.callvan_create_participants_increment), | |
| tint = RebrandKoinTheme.colors.primary500, | |
| modifier = Modifier | |
| .clickable(onClick = onIncrement) | |
| .padding(horizontal = 12.dp, vertical = 12.dp) | |
| .size(24.dp) | |
| ) |
strings.xml에
callvan_create_participants_increment("인원 증가") 리소스 추가 필요
| withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) { | ||
| append(stringResource(R.string.callvan_create_participants_hint_bold)) | ||
| } | ||
| append(stringResource(R.string.callvan_create_participants_hint_normal)) | ||
| }, | ||
| style = RebrandKoinTheme.typography.regular12, |
There was a problem hiding this comment.
[Minor] 불필요한 리컴포지션: buildAnnotatedString이 매 컴포지션마다 재생성됨
이 힌트 텍스트는 어떤 상태에도 의존하지 않지만, CallvanParticipantsSection이 리컴포지션될 때마다 buildAnnotatedString이 새 객체를 생성합니다. remember로 캐싱하면 불필요한 객체 생성을 방지할 수 있습니다.
| withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) { | |
| append(stringResource(R.string.callvan_create_participants_hint_bold)) | |
| } | |
| append(stringResource(R.string.callvan_create_participants_hint_normal)) | |
| }, | |
| style = RebrandKoinTheme.typography.regular12, | |
| text = remember { | |
| buildAnnotatedString { | |
| withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) { | |
| append(stringResource(R.string.callvan_create_participants_hint_bold)) | |
| } | |
| append(stringResource(R.string.callvan_create_participants_hint_normal)) | |
| } | |
| }, |
단,
stringResource는 Composable 컨텍스트가 필요하므로 변수로 먼저 추출하는 방법도 유효합니다:val boldHint = stringResource(R.string.callvan_create_participants_hint_bold) val normalHint = stringResource(R.string.callvan_create_participants_hint_normal) val hintText = remember(boldHint, normalHint) { buildAnnotatedString { withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) { append(boldHint) } append(normalHint) } }
| modifier = Modifier | ||
| .border(width = 1.dp, color = RebrandKoinTheme.colors.neutral400) | ||
| .padding(horizontal = 24.dp, vertical = 8.dp), | ||
| contentAlignment = Alignment.Center | ||
| ) { | ||
| Text( | ||
| text = count.toString(), | ||
| style = RebrandKoinTheme.typography.regular14, | ||
| color = RebrandKoinTheme.colors.neutral800 | ||
| ) | ||
| } | ||
| Icon( |
There was a problem hiding this comment.
[Info] 카운트가 두 곳에서 표시됨 — 의도된 디자인인지 확인 필요
현재 인원 수가 두 군데에 렌더링됩니다:
- 왼쪽:
participantsText→"4 명"(포맷된 문자열) - 중앙 Box:
count.toString()→"4"(숫자만)
디자인 스펙상 의도된 레이아웃이라면 문제없지만, 만약 왼쪽의 "4 명" 텍스트가 필요 없다면 제거를 검토해 주세요. 또한 중앙 Box의 숫자 텍스트에도 contentDescription이 있으면 접근성이 향상됩니다.
| isSubmitting: Boolean, | ||
| modifier: Modifier = Modifier, | ||
| onSubmit: () -> Unit = {} | ||
| ) { |
There was a problem hiding this comment.
[Minor] 제출 콜백의 기본값 {} — 조용한 실패(silent failure) 위험
onSubmit: () -> Unit = {}에 기본값 {}을 주면, 상위 컴포저블에서 실수로 콜백을 전달하지 않아도 컴파일 오류 없이 버튼을 눌러도 아무 일도 일어나지 않습니다. 제출 버튼은 핵심 기능이므로 콜백을 필수 파라미터로 만드는 것을 권장합니다.
| ) { | |
| onSubmit: () -> Unit |
반면 modifier: Modifier = Modifier처럼 순수 UI 파라미터의 기본값은 적절합니다.
| text = stringResource(R.string.callvan_create_submit), | ||
| onClick = onSubmit, | ||
| enabled = isFormComplete && !isSubmitting, | ||
| modifier = Modifier.fillMaxWidth(), | ||
| shape = RebrandKoinTheme.shapes.small, | ||
| textStyle = RebrandKoinTheme.typography.bold16, | ||
| contentPadding = PaddingValues(vertical = 14.dp), | ||
| colors = ButtonDefaults.buttonColors( | ||
| containerColor = RebrandKoinTheme.colors.primary500, | ||
| disabledContainerColor = RebrandKoinTheme.colors.neutral400, | ||
| contentColor = RebrandKoinTheme.colors.neutral0, | ||
| disabledContentColor = RebrandKoinTheme.colors.neutral0 | ||
| ) | ||
| ) | ||
| } |
There was a problem hiding this comment.
[Minor] isSubmitting 중 로딩 피드백 없음 — UX 문제
isSubmitting = true일 때 버튼이 비활성화되지만, 사용자 입장에서는 버튼이 왜 눌리지 않는지 알기 어렵습니다. 로딩 인디케이터를 추가하면 제출 진행 중임을 명확히 전달할 수 있습니다.
| text = stringResource(R.string.callvan_create_submit), | |
| onClick = onSubmit, | |
| enabled = isFormComplete && !isSubmitting, | |
| modifier = Modifier.fillMaxWidth(), | |
| shape = RebrandKoinTheme.shapes.small, | |
| textStyle = RebrandKoinTheme.typography.bold16, | |
| contentPadding = PaddingValues(vertical = 14.dp), | |
| colors = ButtonDefaults.buttonColors( | |
| containerColor = RebrandKoinTheme.colors.primary500, | |
| disabledContainerColor = RebrandKoinTheme.colors.neutral400, | |
| contentColor = RebrandKoinTheme.colors.neutral0, | |
| disabledContentColor = RebrandKoinTheme.colors.neutral0 | |
| ) | |
| ) | |
| } | |
| FilledButton( | |
| text = if (isSubmitting) "" else stringResource(R.string.callvan_create_submit), | |
| onClick = onSubmit, | |
| enabled = isFormComplete && !isSubmitting, | |
| modifier = Modifier.fillMaxWidth(), | |
| shape = RebrandKoinTheme.shapes.small, | |
| textStyle = RebrandKoinTheme.typography.bold16, | |
| contentPadding = PaddingValues(vertical = 14.dp), | |
| colors = ButtonDefaults.buttonColors( | |
| containerColor = RebrandKoinTheme.colors.primary500, | |
| disabledContainerColor = RebrandKoinTheme.colors.neutral400, | |
| contentColor = RebrandKoinTheme.colors.neutral0, | |
| disabledContentColor = RebrandKoinTheme.colors.neutral0 | |
| ) | |
| ) { | |
| if (isSubmitting) { | |
| CircularProgressIndicator( | |
| modifier = Modifier.size(20.dp), | |
| color = RebrandKoinTheme.colors.neutral0, | |
| strokeWidth = 2.dp | |
| ) | |
| } | |
| } |
FilledButton이 content slot을 지원하지 않는 경우,Box로 버튼 위에CircularProgressIndicator를 오버레이하는 방식도 가능합니다.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt`:
- Around line 87-115: Replace the two standalone Icon composables used for
increment/decrement (the Icon with
ImageVector.vectorResource(R.drawable.ic_remove) using onDecrement and the Icon
with Icons.Default.Add using onIncrement) with IconButton wrappers and provide
explicit contentDescription strings via stringResource (e.g.,
stringResource(R.string.decrease_participants) and
stringResource(R.string.increase_participants)); keep the same imageVector,
tint, size and padding, move the clickable behavior to the IconButton (onClick =
onDecrement / onClick = onIncrement), and remove contentDescription = null so
screen readers can announce the buttons.
🪄 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.yaml
Review profile: CHILL
Plan: Pro
Run ID: 15f23322-dc7b-45a7-b4fa-cbadf73f52bc
📒 Files selected for processing (1)
feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt
| Icon( | ||
| imageVector = ImageVector.vectorResource(R.drawable.ic_remove), | ||
| contentDescription = null, | ||
| tint = RebrandKoinTheme.colors.primary500, | ||
| modifier = Modifier | ||
| .clickable(onClick = onDecrement) | ||
| .padding(horizontal = 8.dp, vertical = 7.dp) | ||
| .size(24.dp) | ||
| ) | ||
| Box( | ||
| modifier = Modifier | ||
| .border(width = 1.dp, color = RebrandKoinTheme.colors.neutral400) | ||
| .padding(horizontal = 24.dp, vertical = 8.dp), | ||
| contentAlignment = Alignment.Center | ||
| ) { | ||
| Text( | ||
| text = count.toString(), | ||
| style = RebrandKoinTheme.typography.regular14, | ||
| color = RebrandKoinTheme.colors.neutral800 | ||
| ) | ||
| } | ||
| Icon( | ||
| imageVector = Icons.Default.Add, | ||
| contentDescription = null, | ||
| tint = RebrandKoinTheme.colors.primary500, | ||
| modifier = Modifier | ||
| .clickable(onClick = onIncrement) | ||
| .padding(horizontal = 8.dp, vertical = 7.dp) | ||
| .size(24.dp) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp/repo && wc -l feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.ktRepository: BCSDLab/KOIN_ANDROID
Length of output: 123
🏁 Script executed:
cd /tmp/repo && sed -n '80,120p' feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.ktRepository: BCSDLab/KOIN_ANDROID
Length of output: 123
🏁 Script executed:
cd /tmp/repo && cat -n feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.ktRepository: BCSDLab/KOIN_ANDROID
Length of output: 123
🏁 Script executed:
cd /tmp/repo && fd -e strings.xml feature/callvan && head -50 feature/callvan/src/main/res/values/strings.xmlRepository: BCSDLab/KOIN_ANDROID
Length of output: 123
🏁 Script executed:
cd /tmp/repo && rg "IconButton" feature/callvan --type kt -A 3 | head -50Repository: BCSDLab/KOIN_ANDROID
Length of output: 123
🏁 Script executed:
wc -l feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.ktRepository: BCSDLab/KOIN_ANDROID
Length of output: 181
🏁 Script executed:
sed -n '80,120p' feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.ktRepository: BCSDLab/KOIN_ANDROID
Length of output: 1625
🏁 Script executed:
cat -n feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.ktRepository: BCSDLab/KOIN_ANDROID
Length of output: 6155
🏁 Script executed:
fd -e strings.xml feature/callvanRepository: BCSDLab/KOIN_ANDROID
Length of output: 429
🏁 Script executed:
rg "IconButton" feature/callvan --type kt -A 3 | head -100Repository: BCSDLab/KOIN_ANDROID
Length of output: 90
🏁 Script executed:
find feature/callvan -name "strings.xml" -type fRepository: BCSDLab/KOIN_ANDROID
Length of output: 111
🏁 Script executed:
cat feature/callvan/src/main/res/values/strings.xmlRepository: BCSDLab/KOIN_ANDROID
Length of output: 6301
🏁 Script executed:
rg "callvan_create_participants" feature/callvan/src/main/res/values/strings.xmlRepository: BCSDLab/KOIN_ANDROID
Length of output: 358
🏁 Script executed:
rg "IconButton" feature --type kotlin -B 2 -A 5Repository: BCSDLab/KOIN_ANDROID
Length of output: 50376
🏁 Script executed:
rg "contentDescription" feature/callvan --type kotlin -B 1 -A 1Repository: BCSDLab/KOIN_ANDROID
Length of output: 10298
🏁 Script executed:
rg "callvan_create_participants" feature/callvan/src/main/res/values/ --type kotlinRepository: BCSDLab/KOIN_ANDROID
Length of output: 46
🏁 Script executed:
grep -r "decrement\|increment" feature/callvan/src/main/res/values/Repository: BCSDLab/KOIN_ANDROID
Length of output: 46
🏁 Script executed:
find feature/callvan/src/main/res/values -name "strings.xml" -exec grep -l "button\|icon\|action" {} \;Repository: BCSDLab/KOIN_ANDROID
Length of output: 46
클릭 가능한 증감 버튼에 접근성 라벨이 없어 스크린리더 사용자가 기능을 식별할 수 없습니다.
88-89번 줄과 109-110번 줄의 클릭 가능한 아이콘 2개가 contentDescription = null로 설정되어 있어 스크린리더 사용자에게 증가/감소 버튼의 의미가 전달되지 않습니다. IconButton으로 변경하고 stringResource를 통해 명시적 contentDescription을 제공해 주세요.
수정 예시
+import androidx.compose.material3.IconButton
@@
- Icon(
- imageVector = ImageVector.vectorResource(R.drawable.ic_remove),
- contentDescription = null,
- tint = RebrandKoinTheme.colors.primary500,
- modifier = Modifier
- .clickable(onClick = onDecrement)
- .padding(horizontal = 8.dp, vertical = 7.dp)
- .size(24.dp)
- )
+ IconButton(onClick = onDecrement) {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_remove),
+ contentDescription = stringResource(R.string.callvan_create_participants_decrement),
+ tint = RebrandKoinTheme.colors.primary500,
+ )
+ }
@@
- Icon(
- imageVector = Icons.Default.Add,
- contentDescription = null,
- tint = RebrandKoinTheme.colors.primary500,
- modifier = Modifier
- .clickable(onClick = onIncrement)
- .padding(horizontal = 8.dp, vertical = 7.dp)
- .size(24.dp)
- )
+ IconButton(onClick = onIncrement) {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = stringResource(R.string.callvan_create_participants_increment),
+ tint = RebrandKoinTheme.colors.primary500,
+ )
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt`
around lines 87 - 115, Replace the two standalone Icon composables used for
increment/decrement (the Icon with
ImageVector.vectorResource(R.drawable.ic_remove) using onDecrement and the Icon
with Icons.Default.Add using onIncrement) with IconButton wrappers and provide
explicit contentDescription strings via stringResource (e.g.,
stringResource(R.string.decrease_participants) and
stringResource(R.string.increase_participants)); keep the same imageVector,
tint, size and padding, move the clickable behavior to the IconButton (onClick =
onDecrement / onClick = onIncrement), and remove contentDescription = null so
screen readers can announce the buttons.
There was a problem hiding this comment.
이 사항은 제외하고 검토 후 approve
There was a problem hiding this comment.
🧩 Analysis chain
@JaeYoung290, 해당 접근성 관련 사항(increment/decrement 아이콘의 contentDescription)은 검토에서 제외하겠습니다. PR을 승인 처리하였습니다.
🏁 Script executed:
gh pr review 1326 --repo BCSDLab/KOIN_ANDROID --approve --body "접근성 관련 사항(contentDescription)은 개발자 요청에 따라 제외하고 검토하였습니다. 나머지 구현 사항에 이상 없어 승인합니다."
Length of output: 0
@JaeYoung290, PR #1326 승인이 완료되었습니다. increment/decrement 아이콘의 contentDescription 관련 접근성 사항은 향후 리뷰에서도 제외하도록 기록해 두겠습니다.
✏️ Learnings added
Learnt from: JaeYoung290
Repo: BCSDLab/KOIN_ANDROID PR: 1326
File: feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt:87-115
Timestamp: 2026-03-16T04:02:16.200Z
Learning: In BCSDLab/KOIN_ANDROID feature/callvan module, the use of `contentDescription = null` on clickable Icon composables (e.g., increment/decrement buttons in CallvanParticipantsSection.kt) is intentional per developer (JaeYoung290) preference. Do not flag missing contentDescription as an accessibility issue in this module.
Learnt from: JaeYoung290
Repo: BCSDLab/KOIN_ANDROID PR: 1326
File: feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt:87-115
Timestamp: 2026-03-16T04:02:16.200Z
Learning: In BCSDLab/KOIN_ANDROID feature/callvan module, the use of `contentDescription = null` on clickable Icon composables (e.g., increment/decrement buttons in CallvanParticipantsSection.kt) is intentional per developer (JaeYoung290) preference. Do not flag missing contentDescription as an accessibility issue in this module.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: feature/article/AGENTS.md:0-0
Timestamp: 2026-02-26T14:13:24.114Z
Learning: Applies to feature/article/src/main/java/**/component/**/*.kt : Create Compose components following design system patterns with Composable annotation, state parameters, and Preview composables
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: core/designsystem/AGENTS.md:0-0
Timestamp: 2026-02-26T14:11:23.114Z
Learning: Applies to core/designsystem/**/component/button/*.kt : Button components must provide loading state, handle overflow text gracefully, support icons, and be accessible with proper content descriptions
Learnt from: KYM-P
Repo: BCSDLab/KOIN_ANDROID PR: 1313
File: feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/component/CallvanPersonIcon.kt:44-55
Timestamp: 2026-03-07T09:47:59.923Z
Learning: In `feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/component/CallvanPersonIcon.kt`, the hardcoded color values `Color(0xFFD39AFE)` (case 1) and `Color(0xFFC969FC)` (case 3) in `callvanPersonIconColor()` are intentional to match design specs. These intermediate purple shades (~primary250 and ~primary350 level) do not have corresponding tokens in `RebrandKoinColorPalette` defined in `core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/theme/Color.kt`, so hardcoding is unavoidable until new palette tokens are added.
Learnt from: JaeYoung290
Repo: BCSDLab/KOIN_ANDROID PR: 1324
File: feature/callvan/src/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanDateField.kt:0-0
Timestamp: 2026-03-15T17:07:10.727Z
Learning: In BCSDLab/KOIN_ANDROID feature/callvan module, import ordering violations should be excluded from code review; user (JaeYoung290) has explicitly requested not to flag import order issues in this module.
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T13:15:27.906Z
Learning: Applies to feature/article/**/*.kt : feature/article module uses hybrid UI: Article/search/keyword screens use XML Fragments + Navigation Component; Lost & Found uses pure Compose with Orbit MVI; new features SHOULD use Compose
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T13:15:27.906Z
Learning: Applies to feature/**/*.kt : feature/* modules (except article) MUST use Jetpack Compose with *Screen + *ScreenImpl pattern and Orbit MVI
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: core/onboarding/AGENTS.md:0-0
Timestamp: 2026-02-26T14:12:12.825Z
Learning: Applies to core/onboarding/src/main/java/**/OnboardingType.kt : Use StringRes annotation for descriptionResId property in OnboardingType enum. Use 0 for descriptionResId when the onboarding type triggers an action instead of showing a tooltip
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: koin/AGENTS.md:0-0
Timestamp: 2026-02-26T14:16:01.759Z
Learning: Applies to koin/src/main/java/in/koreatech/koin/**/{*Activity,*Fragment}.kt : MUST embed new Compose widgets via `ComposeView.setContent {}` within XML layouts instead of creating full Compose screens
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T13:15:27.906Z
Learning: Applies to koin/**/*.kt : New features in koin/ module SHOULD embed Compose widgets via ComposeView.setContent {} within XML layouts (NOT full Compose screens)
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: core/designsystem/AGENTS.md:0-0
Timestamp: 2026-02-26T14:11:23.114Z
Learning: Applies to core/designsystem/**/component/dialog/*.kt : Create dialog components using scale-based color tokens and weight+size typography tokens, handle dismiss request properly, and follow Material Design dialog patterns
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2026-02-26T14:10:51.888Z
Learning: Applies to core/**/*.kt : MUST maintain backwards compatibility when modifying utilities used across modules. SHOULD add Deprecated annotations with replaceWith when utilities are superseded by Compose equivalents.
Learnt from: CR
Repo: BCSDLab/KOIN_ANDROID PR: 0
File: feature/store/AGENTS.md:0-0
Timestamp: 2026-02-26T14:15:11.068Z
Learning: Applies to feature/store/src/main/java/in/koreatech/koin/feature/store/ui/**/*.kt : Follow the two-function Compose pattern: outer screen function handles ViewModel state collection and side effects, inner Impl function contains composable UI implementation with preview
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
...ain/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt
Show resolved
Hide resolved
...ain/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt
Show resolved
Hide resolved
...ain/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt
Show resolved
Hide resolved
| Text( | ||
| text = buildAnnotatedString { | ||
| withStyle(RebrandKoinTheme.typography.bold12.toSpanStyle()) { | ||
| append(stringResource(R.string.callvan_create_participants_hint_bold)) | ||
| } | ||
| append(stringResource(R.string.callvan_create_participants_hint_normal)) | ||
| }, |
There was a problem hiding this comment.
[Minor] buildAnnotatedString을 remember로 감싸지 않음
힌트 문자열은 컴포지션 내에서 절대 변하지 않는 정적 값이지만, 현재는 리컴포지션이 발생할 때마다 buildAnnotatedString 블록이 새로운 AnnotatedString 객체를 생성합니다. remember로 캐싱하면 불필요한 객체 생성을 방지할 수 있습니다.
| Text( | |
| text = buildAnnotatedString { | |
| withStyle(RebrandKoinTheme.typography.bold12.toSpanStyle()) { | |
| append(stringResource(R.string.callvan_create_participants_hint_bold)) | |
| } | |
| append(stringResource(R.string.callvan_create_participants_hint_normal)) | |
| }, | |
| text = remember { | |
| buildAnnotatedString { | |
| withStyle(RebrandKoinTheme.typography.bold12.toSpanStyle()) { | |
| append(stringResource(R.string.callvan_create_participants_hint_bold)) | |
| } | |
| append(stringResource(R.string.callvan_create_participants_hint_normal)) | |
| } | |
| }, |
참고:
stringResource는@Composable함수 안에서만 호출 가능하므로,remember블록 내에서 직접 호출하려면 미리 변수로 추출해야 합니다.
val boldText = stringResource(R.string.callvan_create_participants_hint_bold)
val normalText = stringResource(R.string.callvan_create_participants_hint_normal)
val hintText = remember(boldText, normalText) {
buildAnnotatedString {
withStyle(RebrandKoinTheme.typography.bold12.toSpanStyle()) { append(boldText) }
append(normalText)
}
}
...ain/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanParticipantsSection.kt
Show resolved
Hide resolved
| ) | ||
| } | ||
| Icon( | ||
| imageVector = Icons.Default.Add, |
There was a problem hiding this comment.
[Trivial] 아이콘 소스 불일치
감소 버튼은 커스텀 벡터(R.drawable.ic_remove)를 사용하고, 증가 버튼은 Material 기본 아이콘(Icons.Default.Add)을 사용합니다. 시각적 일관성을 위해 두 아이콘을 같은 소스에서 사용하는 것이 좋습니다. 커스텀 아이콘 R.drawable.ic_add가 있다면 이를 활용하세요.
| imageVector = Icons.Default.Add, | |
| imageVector = ImageVector.vectorResource(R.drawable.ic_add), |
| FilledButton( | ||
| text = stringResource(R.string.callvan_create_submit), | ||
| onClick = onSubmit, | ||
| enabled = isFormComplete && !isSubmitting, | ||
| modifier = Modifier.fillMaxWidth(), | ||
| shape = RebrandKoinTheme.shapes.small, | ||
| textStyle = RebrandKoinTheme.typography.bold16, | ||
| contentPadding = PaddingValues(vertical = 14.dp), | ||
| colors = ButtonDefaults.buttonColors( | ||
| containerColor = RebrandKoinTheme.colors.primary500, | ||
| disabledContainerColor = RebrandKoinTheme.colors.neutral400, | ||
| contentColor = RebrandKoinTheme.colors.neutral0, | ||
| disabledContentColor = RebrandKoinTheme.colors.neutral0 | ||
| ) |
There was a problem hiding this comment.
[Major] isSubmitting 상태에서 로딩 UI 없음
isSubmitting = true일 때 버튼이 비활성화되기만 할 뿐, 사용자에게 처리 중임을 알리는 시각적 피드백이 전혀 없습니다. 프리뷰 이름도 "Loading"이지만 실제 로딩 인디케이터가 없어 앱이 멈춘 것처럼 보일 수 있습니다.
FilledButton이 로딩 상태를 지원한다면:
FilledButton(
text = if (isSubmitting) "" else stringResource(R.string.callvan_create_submit),
onClick = onSubmit,
enabled = isFormComplete && !isSubmitting,
// ...
)또는 버튼 내부에 CircularProgressIndicator를 조건부로 표시하는 방식을 고려하세요:
if (isSubmitting) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = RebrandKoinTheme.colors.neutral0,
strokeWidth = 2.dp
)
} else {
Text(text = stringResource(R.string.callvan_create_submit))
}FilledButton 컴포넌트가 isLoading 파라미터를 제공한다면 그것을 활용하는 것이 가장 이상적입니다.
| Column(modifier = modifier.fillMaxWidth()) { | ||
| HorizontalDivider(color = RebrandKoinTheme.colors.neutral100) | ||
| Column( |
There was a problem hiding this comment.
[Trivial] 불필요한 Column 중첩
외부 Column은 HorizontalDivider와 내부 Column 하나만 담고 있습니다. Column 대신 modifier를 직접 내부 레이아웃에 적용하고 HorizontalDivider를 형제 요소로 두면 중첩을 줄일 수 있습니다. 다만 이 구조는 호출부에서 modifier를 바깥 컨테이너에 적용해야 할 때 유용할 수 있으므로 현재 맥락에서 의도된 것이라면 무시해도 됩니다.
...rc/main/java/in/koreatech/koin/feature/callvan/ui/create/component/CallvanSubmitBottomBar.kt
Show resolved
Hide resolved
|
KYM-P
left a comment
There was a problem hiding this comment.
Remember 상태
로딩 화면
정도는 필요해 보입니다.



PR 개요
이슈 번호: #1283
PR 체크리스트
작업사항
작업사항의 상세한 설명
참여 인원 선택 섹션과 글쓰기 제출 바텀바를 추가합니다.
CallvanParticipantsSection: ± 버튼으로 최대 인원(1~8명) 조절CallvanSubmitBottomBar: 폼 완성 여부에 따른 버튼 활성화/비활성화 처리논의 사항
스크린샷
추가내용
Summary by CodeRabbit