[design] #108 디자인 시스템 변경사항 반영#113
Conversation
`FilterBar`, `DownIconFilterChip`, `DefaultFilterChip` 컴포저블에 `chipShape` 파라미터를 추가하여 필터칩의 모양을 외부에서 설정할 수 있도록 기능을 확장했습니다.
- titleLabel, textStyle, cursorBrush, lineLimits 파라미터 추가 - NekiTextFieldWithError 컴포넌트 추가 (에러 메시지 영역 포함) - NekiTextFieldWithLabel 제거, titleLabel 파라미터로 통합 - 프리뷰 4개 추가
- ToolTipColor enum 추가 (Gray800/Gray25 배경·텍스트·아이콘 색상 매핑) - ArrowDirection(Up/Down), arrowAlignment, arrowPosition 파라미터 도입 - TriangleArrow 컴포넌트 분리 - hasCloseButton 지원 - 프리뷰 24개 추가 (방향·정렬·컬러·닫기버튼 조합)
- PhotoGridItemOverlay → GridItemOverlay 리네임 - SelectedPhotoGridItemOverlay 프리뷰 추가 - 사용처 반영 (SelectablePhotoItem, ArchiveMainPhotoItem)
- TopBarTextButton 삭제 - NekiTopBar 수정
- enabledTextColor, disabledTextColor 파라미터 추가 - MaterialTheme → NekiTheme 색상으로 변경
- photoBackground(), poseBackground() Modifier 삭제 - PoseListContent에서 poseBackground() → GridItemOverlay로 교체
- 타이틀, 옵션, 버튼 영역 패딩 및 간격 재조정
- 토치 버튼 48dp → 56dp, 아이콘 28dp 명시
- buttonShadow() 추가, 아이콘 20dp → 24dp
|
Caution Review failedThe pull request is closed. Walkthrough디자인 시스템에 새로운 텍스트 필드(NekiTextField / NekiTextFieldWithError)와 액션바(NekiStart/End/BothSides), 드롭다운 팝업, 툴팁 확장, 대시 스트로크 수정자 및 여러 UI 컴포넌트 리팩토링/교체가 추가되었고, 여러 피처 모듈에서 해당 컴포넌트로 마이그레이션했습니다. Changes
Sequence Diagram(s)(생성 조건 미충족 — 변경은 주로 UI 컴포넌트 추가 및 리팩토링으로, 다중 컴포넌트 간의 명확한 시퀀스 흐름을 시각화할 새로운 제어 흐름은 포함되지 않아 시퀀스 다이어그램을 생략합니다.) Estimated code review effort🎯 3 (Moderate) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ 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 |
공통으로 사용할 수 있는 `DropdownPopup` 컴포넌트를 `core/ui` 모듈에 추가했습니다. 기존 `AllPhotoFilterBar`에서 사용하던 Popup 로직을 새로운 공용 컴포넌트로 대체하여 재사용성을 높였습니다.
보관함의 사진 상세 화면에서 다운로드 및 삭제 아이콘의 활성화 시 색상을 `gray600`에서 `gray700`으로 변경했습니다.
`background`와 `hazeSource`의 순서를 변경하여 잠재적인 UI 문제를 해결하고, 미리보기 컴포저블에 패딩을 추가하여 레이아웃을 개선했습니다.
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt (1)
123-157:⚠️ Potential issue | 🟡 Minor
AddAlbumItem이ArchiveMainAlbumList에서 사용되지 않습니다.
AddAlbumItem컴포저블이 정의되어 있지만ArchiveMainAlbumList의LazyRow에 포함되지 않습니다. 프리뷰에서만 참조됩니다. 새 앨범 추가 아이템이 앨범 리스트에 표시되어야 한다면,LazyRow에 아이템으로 추가하고onClickAddAlbumItem콜백을 파라미터로 노출해야 합니다.feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailScreen.kt (1)
80-88:⚠️ Potential issue | 🟠 MajorHorizontalDivider가 중복 렌더링됩니다.
Line 80-84의
HorizontalDivider와PoseActionBar내부의NekiEndActionBar가 자체적으로 렌더링하는HorizontalDivider가 중복됩니다 (NekiActionBar.ktLine 51-54 참조). 두 개의 구분선이 연속으로 표시됩니다.🐛 중복 Divider 제거 제안
- HorizontalDivider( - modifier = Modifier.fillMaxWidth(), - thickness = 1.dp, - color = NekiTheme.colorScheme.gray75, - ) PoseActionBar(
🤖 Fix all issues with AI agents
In
`@core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt`:
- Around line 150-157: ToolTipContent currently renders a close Icon when
hasCloseButton is true but has no click handler; add an onDismissRequest: () ->
Unit parameter to ToolTipContent, wrap the Icon in an IconButton (or add
Modifier.clickable) to call onDismissRequest when tapped, and update the caller
ToolTipPopup to forward its dismiss callback into ToolTipContent so the close
button actually dismisses the popup.
In `@core/designsystem/src/main/res/drawable/icon_close.xml`:
- Around line 2-5: NekiIconButton.kt is missing the tint on the Icon composable,
so the icon can render with the wrong color; update the Icon call inside the
NekiIconButton composable (or function named NekiIconButton) to pass a tint
argument set to the dark gray color used elsewhere (hex `#3C3E48`) — e.g., use
Color(0xFF3C3E48) or the project color resource equivalent — ensuring the
Icon(tint = ...) mirrors other icon_close usages.
In `@core/designsystem/src/main/res/drawable/icon_heart_filled.xml`:
- Around line 1-9: The filled heart's pathData differs from the stroked heart
causing a visual shift when toggling; update icon_heart_filled to use the exact
same android:pathData as icon_heart_stroked (or vice versa) and keep only the
fillColor/strokeColor differences (android:fillColor vs android:strokeColor) so
the shapes are identical during toggle; locate the android:pathData attribute in
icon_heart_filled and replace it with the pathData string from
icon_heart_stroked (or copy the filled one into the stroked XML) and ensure
viewBox/viewportWidth/height remain consistent.
In
`@core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt`:
- Around line 149-176: AlbumThumbnail incorrectly only checks isEmpty and
ignores thumbnailUrl and the passed modifier: update AlbumThumbnail to treat
thumbnailUrl == null the same as isEmpty (use the same condition
FavoriteAlbumThumbnail uses, e.g., isEmpty || thumbnailUrl == null) so you don't
pass a null model to AsyncImage, and in the empty branch apply the incoming
modifier plus clip(RoundedCornerShape(8.dp)) and background (matching the
non-empty branch) instead of creating a fresh Modifier to ensure visual
consistency between branches; adjust references around AlbumThumbnail,
FavoriteAlbumThumbnail, AsyncImage, thumbnailUrl, isEmpty and modifier
accordingly.
In `@core/ui/src/main/java/com/neki/android/core/ui/component/DropdownPopup.kt`:
- Around line 50-58: In DropdownPopup.kt inside the Text composable (the block
that uses selectedItem and onSelect), the modifier chain currently applies
.background() then .clickableSingle() then .padding(), which excludes the
padding from the clickable area; move .padding(...) to come before
.clickableSingle { onSelect(item) } so the touch target includes the padding
while keeping .background(...) where it is.
In `@core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt`:
- Around line 45-56: The Box's border is using a hardcoded
RoundedCornerShape(8.dp) which can diverge from the passed-in shape; update the
border call in ItemOverlay (the Box modifier chain where border(...) and
background(...) are applied) to use the existing shape parameter instead of
RoundedCornerShape(8.dp) so border and background always share the same shape.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt`:
- Around line 207-242: The thumbnail image currently has a duplicated elevation
modifier: outer Box already applies .cardShadow() and clips with
RoundedCornerShape, so remove the inner .cardShadow(shape =
RoundedCornerShape(8.dp)) applied to the AsyncImage to avoid double shadow; keep
the AsyncImage modifiers background, matchParentSize and hazeSource(hazeState)
so the visual and clipping behavior remains correct (look for the AsyncImage
block and its modifiers to update).
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoScreen.kt`:
- Around line 125-129: EmptyAlbumContent is rendering an album-specific
hardcoded message so update AllPhotoScreen (AllPhotoScreen.kt) to pass a
context-appropriate emptyText (e.g. emptyText = "모든 사진이 없습니다" or another
localized string) when calling EmptyAlbumContent, and modify the
EmptyAlbumContent composable signature to accept a new emptyText: String
parameter with a sensible default (to avoid breaking other callers) and use that
value instead of the hardcoded "앨범에 추가해보세요"; keep the existing onClickBack
behavior (onIntent(AllPhotoIntent.ClickTopBarBackIcon)).
In
`@feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt`:
- Around line 29-35: In RecommendationChip's Row modifier chain, swap the order
of .clip(CircleShape) and .buttonShadow() so the shadow is applied before
clipping (follow the pattern used in ToMapChip, MapRefreshChip, CloseButton);
locate the modifier block in RecommendationChip.kt and change the chain to call
.buttonShadow() first, then .clip(CircleShape), keeping the rest
(.clickableSingle, .background, .padding) unchanged.
🧹 Nitpick comments (18)
core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Stroke.kt (1)
12-29: 스트로크가 컴포넌트 경계에 그려져 큰strokeWidth값에서 잘릴 수 있습니다.
drawRoundRect은 컴포넌트 전체 영역(size)에 그려지므로 스트로크 두께의 절반이 경계 밖으로 나가 클리핑됩니다. 기본값1.dp에서는 거의 눈에 띄지 않지만, 더 두꺼운 스트로크를 사용할 경우strokeWidth / 2만큼 inset을 적용하는 것이 정확합니다.♻️ inset 적용 제안
): Modifier = this.drawBehind { + val inset = strokeWidth.toPx() / 2 drawRoundRect( color = color, + topLeft = androidx.compose.ui.geometry.Offset(inset, inset), + size = androidx.compose.ui.geometry.Size(size.width - inset * 2, size.height - inset * 2), style = Stroke( width = strokeWidth.toPx(), pathEffect = PathEffect.dashPathEffect( intervals = floatArrayOf(dashLength.toPx(), gapLength.toPx()), phase = 0f, ), ), - cornerRadius = CornerRadius(cornerRadius.toPx()), + cornerRadius = CornerRadius((cornerRadius - strokeWidth / 2).toPx().coerceAtLeast(0f)), ) }feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/QRScannerContent.kt (1)
161-181: 토치 버튼의 색상이 하드코딩되어 있습니다.닫기 아이콘(Line 125)은
NekiTheme.colorScheme.white로 테마 색상을 적용했지만, 토치 버튼의 배경색(Line 167-168)과 아이콘 tint(Line 179)는 여전히Color.White/Color.Black을 직접 사용하고 있습니다. 테마 일관성을 위해 이 부분도NekiTheme.colorScheme을 사용하는 것을 고려해 보세요.♻️ 제안
.background( - if (isTorchEnabled) Color.White - else Color.White.copy(alpha = 0.1f), + if (isTorchEnabled) NekiTheme.colorScheme.white + else NekiTheme.colorScheme.white.copy(alpha = 0.1f), ),- tint = if (isTorchEnabled) Color.Black else Color.White, + tint = if (isTorchEnabled) NekiTheme.colorScheme.black else NekiTheme.colorScheme.white,core/ui/src/main/java/com/neki/android/core/ui/component/DoubleButtonOptionBottomSheet.kt (1)
138-161: 선택 상태에 따른 텍스트 수평 이동 검토 필요.
isSelected가true일 때만 체크 아이콘이 렌더링되어, 선택/미선택 전환 시 텍스트가 수평으로 밀리는 현상이 발생합니다. 의도된 디자인이라면 무시해도 되지만, 시각적 안정감을 위해 미선택 시에도 동일 크기의 빈 공간(Spacer)을 유지하는 방안을 고려해볼 수 있습니다.♻️ 선택/미선택 시 일관된 레이아웃 예시
Row( modifier = modifier .fillMaxWidth() .noRippleClickable(onClick = onClick) .padding(horizontal = 20.dp, vertical = 14.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - if (isSelected) { - Icon( - modifier = Modifier.size(24.dp), - imageVector = ImageVector.vectorResource(R.drawable.icon_check), - contentDescription = null, - tint = NekiTheme.colorScheme.primary500, - ) - } + Icon( + modifier = Modifier.size(24.dp), + imageVector = ImageVector.vectorResource(R.drawable.icon_check), + contentDescription = null, + tint = if (isSelected) NekiTheme.colorScheme.primary500 else NekiTheme.colorScheme.white, + )core/designsystem/src/main/java/com/neki/android/core/designsystem/NekiTextField.kt (2)
36-126:maxLength파라미터가 입력을 제한하지 않고 카운터만 표시합니다.
maxLength는 시각적 카운터("${textFieldState.text.length}/$maxLength")만 렌더링하며, 실제 입력 길이를 제한하지 않습니다. 호출자가 별도로InputTransformation.maxLength()를 전달해야 하므로, 카운터와 실제 제한이 불일치할 수 있습니다.두 가지 접근을 고려해 주세요:
maxLength가 설정되면 내부적으로InputTransformation을 체이닝하여 자동 제한- 또는 파라미터 이름을
maxLengthDisplay등으로 변경하여 의도를 명확히옵션 1: maxLength 설정 시 자동으로 입력 제한 적용
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, ) { + val effectiveInputTransformation = if (maxLength != null && inputTransformation != null) { + inputTransformation.then(InputTransformation.maxLength(maxLength)) + } else if (maxLength != null) { + InputTransformation.maxLength(maxLength) + } else { + inputTransformation + } + val interactionSource = remember { MutableInteractionSource() }그리고
BasicTextField에서inputTransformation대신effectiveInputTransformation을 사용합니다.
164-169: 에러가 없을 때도 빈Text가 항상 렌더링되어 16dp 공간을 차지합니다.레이아웃 안정성을 위한 의도적 설계로 보이지만, 에러 메시지가 필요 없는 곳에서
NekiTextFieldWithError를 사용하면 불필요한 하단 여백이 생깁니다. 현재 동작이 의도된 것이라면 KDoc 등으로 이 동작을 명시해 주세요.feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt (1)
115-124:maxLength가 시각적 카운터와 입력 제한에 중복 지정되어 있습니다.
maxLength = MyPageConst.NICKNAME_MAX_LENGTH(카운터 표시)와inputTransformation = InputTransformation.maxLength(MyPageConst.NICKNAME_MAX_LENGTH)(입력 제한)가 동일한 값으로 별도 전달되고 있습니다. 현재NekiTextFieldAPI 구조상 필요한 패턴이지만, 앞서NekiTextField에 제안한 자동 제한 적용이 반영되면inputTransformation전달을 제거할 수 있습니다.core/designsystem/src/main/java/com/neki/android/core/designsystem/bottomsheet/NekiTextFieldBottomSheet.kt (1)
109-120:NekiTextFieldWithError를 감싸는 외부Column이 불필요합니다.
NekiTextFieldWithError는 내부적으로Column(verticalArrangement = Arrangement.spacedBy(6.dp))을 이미 포함하고 있으므로, 외부에서 동일한 spacing의Column으로 한 번 더 감쌀 필요가 없습니다.♻️ 불필요한 래핑 제거
Spacer(modifier = Modifier.height(16.dp)) // TextField - Column( - verticalArrangement = Arrangement.spacedBy(6.dp), - ) { - NekiTextFieldWithError( - textFieldState = textFieldState, - placeholder = placeholder, - maxLength = maxLength, - isError = isError, - keyboardOptions = keyboardOptions, - errorMessage = errorMessage, - ) - } + NekiTextFieldWithError( + textFieldState = textFieldState, + placeholder = placeholder, + maxLength = maxLength, + isError = isError, + keyboardOptions = keyboardOptions, + errorMessage = errorMessage, + )feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyAlbumContent.kt (2)
27-30: 하드코딩된emptyText가 모든 사용처에 적합하지 않습니다.
EmptyAlbumContent는AlbumDetailScreen과AllPhotoScreen모두에서 사용되지만, "새로운 사진을 등록하고 앨범에 추가해보세요!"라는 메시지는 "모든 사진" 화면에서는 맥락에 맞지 않습니다.emptyText를 파라미터로 추출하면 각 화면에 맞는 메시지를 전달할 수 있습니다.♻️ emptyText를 파라미터로 추출하는 제안
`@Composable` internal fun EmptyAlbumContent( title: String, + emptyText: String, modifier: Modifier = Modifier, onClickBack: () -> Unit = {}, ) { Box( modifier = modifier.fillMaxSize(), ) { EmptyTopBar( title = title, onClickBack = onClickBack, ) EmptyContent( modifier = Modifier.align(Alignment.Center), - emptyText = "아직 등록된 사진이 없어요\n새로운 사진을 등록하고 앨범에 추가해보세요!", + emptyText = emptyText, ) } }
34-45:EmptyTopBar는BackTitleTopBar에 대한 불필요한 래퍼입니다.단순히 파라미터 이름만 바꿔서 위임하고 있으므로 호출 지점에서
BackTitleTopBar를 직접 사용하는 것이 더 간결합니다.core/designsystem/src/main/res/drawable/icon_repeat.xml (1)
1-34: 하드코딩된 strokeColor 대신 중립 색상 사용 권장모든 path에서
strokeColor가#FF5647로 하드코딩되어 있습니다.RecommendationChip에서는tint로 덮어쓰므로 현재는 문제가 없지만, 다른 곳에서 tint 없이 사용하면 테마와 무관한 색상이 노출됩니다. 재사용 가능한 아이콘은#000000등 중립 색상을 사용하고 사용처에서 tint로 제어하는 것이 일반적입니다.core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt (1)
260-261: 프리뷰 어노테이션 불일치:@Previewvs@ComponentPreview
ToolTipArrowUpPreview와ToolTipArrowDownPreview는@ComponentPreview를 사용하지만,ToolTipArrowUpGrayPreview와ToolTipArrowDownGrayPreview는@Preview를 사용합니다. 의도된 차이가 아니라면 일관되게@ComponentPreview로 통일하는 것이 좋겠습니다.Also applies to: 311-312
core/ui/src/main/java/com/neki/android/core/ui/component/DropdownPopup.kt (1)
23-65: 재사용 가능한 제네릭 DropdownPopup 컴포넌트 구현이 잘 되었습니다.
ImmutableList<T>사용,itemLabel람다를 통한 라벨 추출,focusable = true설정이 적절합니다.한 가지 고려사항: 아이템 수가 많아질 경우를 대비하여
Column에verticalScroll또는LazyColumn적용을 고려해 볼 수 있습니다. 현재 사용처(필터 옵션 등)에서는 아이템 수가 적으므로 당장은 문제없지만, 재사용 컴포넌트로서 확장성을 위해 참고해 주세요.core/designsystem/src/main/res/drawable/icon_empty_gallery_thumbnail.xml (1)
12-19: 경로 2와 경로 3의 pathData가 동일합니다.Line 13과 line 17의
pathData가 완전히 동일합니다. 경로 2는 fill만, 경로 3은 stroke만 적용하고 있는데, 하나의<path>로 합칠 수 있습니다. SVG 내보내기 도구의 아티팩트일 수 있으니 확인해 주세요.♻️ 단일 경로로 합치는 제안
- <path - android:pathData="M20.975,2.344C21.057,2.018 21.52,2.018 21.602,2.344L22.096,4.319C22.292,5.101 22.902,5.712 23.683,5.907L25.655,6.4C25.981,6.482 25.981,6.946 25.655,7.027L23.682,7.521C22.9,7.716 22.29,8.326 22.094,9.108L21.602,11.078C21.521,11.404 21.057,11.404 20.975,11.078L20.483,9.108C20.288,8.327 19.677,7.716 18.895,7.521L16.922,7.027C16.595,6.946 16.595,6.482 16.922,6.4L18.894,5.907C19.676,5.712 20.286,5.102 20.481,4.32L20.975,2.344Z" - android:fillColor="#CDCED5"/> - <path - android:strokeWidth="1" - android:pathData="M20.975,2.344C21.057,2.018 21.52,2.018 21.602,2.344L22.096,4.319C22.292,5.101 22.902,5.712 23.683,5.907L25.655,6.4C25.981,6.482 25.981,6.946 25.655,7.027L23.682,7.521C22.9,7.716 22.29,8.326 22.094,9.108L21.602,11.078C21.521,11.404 21.057,11.404 20.975,11.078L20.483,9.108C20.288,8.327 19.677,7.716 18.895,7.521L16.922,7.027C16.595,6.946 16.595,6.482 16.922,6.4L18.894,5.907C19.676,5.712 20.286,5.102 20.481,4.32L20.975,2.344Z" - android:fillColor="#00000000" - android:strokeColor="#CDCED5"/> + <path + android:pathData="M20.975,2.344C21.057,2.018 21.52,2.018 21.602,2.344L22.096,4.319C22.292,5.101 22.902,5.712 23.683,5.907L25.655,6.4C25.981,6.482 25.981,6.946 25.655,7.027L23.682,7.521C22.9,7.716 22.29,8.326 22.094,9.108L21.602,11.078C21.521,11.404 21.057,11.404 20.975,11.078L20.483,9.108C20.288,8.327 19.677,7.716 18.895,7.521L16.922,7.027C16.595,6.946 16.595,6.482 16.922,6.4L18.894,5.907C19.676,5.712 20.286,5.102 20.481,4.32L20.975,2.344Z" + android:fillColor="#CDCED5" + android:strokeWidth="1" + android:strokeColor="#CDCED5"/>feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt (1)
186-191: 하드코딩된 한국어 문자열
"새 앨범 추가"문자열이 하드코딩되어 있습니다. 다국어 지원(i18n)을 고려한다면stringResource를 사용하는 것이 바람직합니다. 현재 프로젝트가 단일 언어만 지원한다면 무시해도 됩니다.core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt (1)
22-89: 세 가지 ActionBar 변형에서 divider 로직이 반복됩니다.
NekiStartActionBar,NekiEndActionBar,NekiBothSidesActionBar모두 동일한HorizontalDivider+Column패턴을 사용합니다. 내부 헬퍼로 추출하면 divider 스타일 변경 시 한 곳만 수정하면 됩니다.♻️ 공통 래퍼 추출 제안
`@Composable` private fun ActionBarLayout( modifier: Modifier = Modifier, content: `@Composable` () -> Unit, ) { Column(modifier = modifier) { HorizontalDivider( modifier = Modifier.fillMaxWidth(), thickness = 1.dp, color = NekiTheme.colorScheme.gray75, ) content() } }이후 각 ActionBar에서
ActionBarLayout을 래핑하여 사용할 수 있습니다.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt (2)
35-59: 터치 영역이 권장 최소 크기(48dp)보다 작습니다.다운로드 및 즐겨찾기 아이콘이
noRippleClickable을 28dpIcon에 직접 적용하고 있어, 터치 영역이 28dp에 불과합니다. 같은 PR의PoseActionBar에서는NekiIconButton(내부적으로IconButton사용, 48dp 최소 터치 영역 보장)을 사용하는 것과 일관되지 않습니다.접근성 가이드라인(Material Design 권장 48dp)에 맞추려면
NekiIconButton을 사용하거나, 각 아이콘에 최소 터치 영역을 확보하는 것이 좋습니다.
62-72: 삭제 아이콘도 동일한 터치 영역 문제가 있습니다.
endContent의 삭제 아이콘도noRippleClickable을 직접 사용하고 있습니다. 위의startContent아이콘들과 동일하게 개선을 권장합니다.core/designsystem/src/main/java/com/neki/android/core/designsystem/topbar/NekiTopBar.kt (1)
55-71:NekiLeftTitleTopBar:actions만 전달될 경우 레이아웃 주의
title이 null이고actions만 전달되면,Row에 자식이 하나뿐이므로SpaceBetween이 의미 없어지고actions가 왼쪽에 배치됩니다. 현재 사용 패턴상 문제가 되지 않을 수 있지만, 방어적으로 처리하면 좋겠습니다.♻️ Spacer 추가로 actions를 항상 오른쪽에 배치하는 방안
) { + if (title != null) { title?.invoke() + } else { + Box(Modifier) + } actions?.invoke() }
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt`:
- Around line 159-194: The AddAlbumItem composable is defined but not used in
ArchiveMainAlbumList's LazyRow and its label is hardcoded; either integrate
AddAlbumItem into ArchiveMainAlbumList's LazyRow (e.g., add it as the first/last
item in the LazyRow data set and call AddAlbumItem(onClick = ...) so it renders
at runtime) or remove the unused private composable; also replace the hardcoded
"새 앨범 추가" string in AddAlbumItem with a string resource reference (use
stringResource(R.string.some_add_album) and add the entry to strings.xml) so the
text is localized.
🧹 Nitpick comments (3)
core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt (1)
134-141:arrowPosition이padding(horizontal = ...)로 사용되어 양쪽에 동일한 패딩이 적용됩니다.
arrowPosition이 화살표의 위치를 나타내는 파라미터인데, 양쪽 수평 패딩으로 적용되고 있습니다.arrowAlignment가CenterStart일 때 왼쪽에서 16dp 떨어진 위치에 화살표가 오는 것은 의도된 동작이지만, 오른쪽에도 동일한 16dp 패딩이 적용되어 넓은 tooltip에서는 레이아웃에 영향을 줄 수 있습니다.start/end패딩을arrowAlignment에 따라 분기하는 방안을 고려해 보세요.💡 방향별 패딩 적용 예시
Box( modifier = Modifier .fillMaxWidth() - .padding(horizontal = arrowPosition), + .padding( + start = if (arrowAlignment == Alignment.CenterStart) arrowPosition else 0.dp, + end = if (arrowAlignment == Alignment.CenterEnd) arrowPosition else 0.dp, + ), contentAlignment = arrowAlignment, ) { TriangleArrow(direction = arrowDirection, color = backgroundColor) }Also applies to: 165-172
feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt (2)
188-188: 하드코딩된 한국어 문자열을 string resource로 추출하는 것을 권장합니다.
"새 앨범 추가"가 직접 하드코딩되어 있습니다. 다국어 지원(i18n) 및 일관성을 위해strings.xml리소스로 분리하는 것이 좋습니다. 같은 파일 내"${photoCount}장"(Line 282)도 동일하게 해당됩니다.
207-213: 외부Box에 명시적 width가 설정되지 않았습니다.
AddAlbumItem은height와width를 모두 명시적으로 설정하지만,ArchiveAlbumItem의 외부Box는height만 설정합니다. 현재는 자식AlbumFolder의width(ARCHIVE_ALBUM_ITEM_WIDTH.dp)에 의해 간접적으로 크기가 결정되지만, 일관성과 견고함을 위해 명시적으로 width를 추가하는 것을 권장합니다.♻️ 제안
Box( modifier = modifier .cardShadow() .height(ARCHIVE_ALBUM_ITEM_HEIGHT.dp) + .width(ARCHIVE_ALBUM_ITEM_WIDTH.dp) .clip(RoundedCornerShape(8.dp)) .noRippleClickable(onClick = onClick), ) {
| @Composable | ||
| private fun AddAlbumItem( | ||
| modifier: Modifier = Modifier, | ||
| onClick: () -> Unit = {}, | ||
| ) { | ||
| Box( | ||
| modifier = modifier | ||
| .height(ARCHIVE_ALBUM_ITEM_HEIGHT.dp) | ||
| .width(ARCHIVE_ALBUM_ITEM_WIDTH.dp) | ||
| .clip(RoundedCornerShape(8.dp)) | ||
| .dashStroke( | ||
| color = NekiTheme.colorScheme.primary400, | ||
| strokeWidth = 2.dp, | ||
| cornerRadius = 8.dp, | ||
| ) | ||
| .noRippleClickable(onClick = onClick), | ||
| ) { | ||
| Column( | ||
| modifier = Modifier.align(Alignment.Center), | ||
| verticalArrangement = Arrangement.spacedBy(8.dp), | ||
| horizontalAlignment = Alignment.CenterHorizontally, | ||
| ) { | ||
| Icon( | ||
| modifier = Modifier.size(28.dp), | ||
| imageVector = ImageVector.vectorResource(R.drawable.icon_plus), | ||
| tint = NekiTheme.colorScheme.primary400, | ||
| contentDescription = null, | ||
| ) | ||
| Text( | ||
| text = "새 앨범 추가", | ||
| style = NekiTheme.typography.body14Medium, | ||
| color = NekiTheme.colorScheme.primary400, | ||
| ) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "AddAlbumItem" --type=kotlinRepository: YAPP-Github/27th-App-Team-2-Android
Length of output: 521
🏁 Script executed:
# LazyRow의 내용을 확인하기 위해 ArchiveMainAlbumList 함수 범위 확인
rg -n "ArchiveMainAlbumList" --type=kotlin -A 50 | head -100Repository: YAPP-Github/27th-App-Team-2-Android
Length of output: 15103
🏁 Script executed:
# 한국어 문자열 리소스 확인
fd -e xml strings | head -20Repository: YAPP-Github/27th-App-Team-2-Android
Length of output: 114
🏁 Script executed:
# 현재 파일의 전체 내용을 확인해서 LazyRow 구조 파악
cat feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt | head -250Repository: YAPP-Github/27th-App-Team-2-Android
Length of output: 8836
AddAlbumItem이 정의되었지만 실제 ArchiveMainAlbumList의 LazyRow에 통합되지 않았습니다.
AddAlbumItem 컴포넌트는 private 접근 제한자로 선언되어 있으며, 파일 내 ArchiveMainAlbumList 함수의 LazyRow에는 포함되지 않습니다. 현재는 Preview 함수에서만 참조되고 있어 런타임에 이 컴포넌트가 표시되지 않습니다. 의도된 기능인 경우 LazyRow에 항목을 추가하고, 미사용 코드인 경우 제거하세요.
또한 line 188의 텍스트 "새 앨범 추가"는 하드코딩되어 있습니다. 다국어 지원을 위해 문자열 리소스로 이동하세요.
🤖 Prompt for AI Agents
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt`
around lines 159 - 194, The AddAlbumItem composable is defined but not used in
ArchiveMainAlbumList's LazyRow and its label is hardcoded; either integrate
AddAlbumItem into ArchiveMainAlbumList's LazyRow (e.g., add it as the first/last
item in the LazyRow data set and call AddAlbumItem(onClick = ...) so it renders
at runtime) or remove the unused private composable; also replace the hardcoded
"새 앨범 추가" string in AddAlbumItem with a string resource reference (use
stringResource(R.string.some_add_album) and add the entry to strings.xml) so the
text is localized.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt (1)
90-90:⚠️ Potential issue | 🟠 Major
textFieldState를 비동기로 로드된 닉네임 값과 동기화해야 합니다.현재 코드에서
rememberTextFieldState(uiState.userInfo.nickname)는 첫 번째 컴포지션에서만 초기값을 설정합니다.uiState.userInfo.nickname이 비동기로 업데이트되면 텍스트 필드는 새로운 값을 반영하지 않습니다.화면의 다른 부분(
displayProfileImage)은 이미LaunchedEffect를 통해 동기화되고 있으므로, 일관성 있게textFieldState도 동기화하세요:제안된 수정
LaunchedEffect(uiState.userInfo.nickname) { if (textFieldState.text.isEmpty() && uiState.userInfo.nickname.isNotEmpty()) { textFieldState.setTextAndPlaceCursorAtEnd(uiState.userInfo.nickname) } }
Ojongseok
left a comment
There was a problem hiding this comment.
고생 많으셨습니다-!!
수정사항만 반영되면 바로 머지하셔도 될 것 같습니다.
사진 상세 화면의 액션바에서 다운로드 및 삭제 아이콘의 색상을 `gray600`에서 `gray700`으로 변경했습니다.
`NekiActionBar` 컴포저블의 뒤로가기 버튼 아이콘(`icon_arrow_left`) 크기를 28dp로 설정하여 디자인을 수정했습니다.
`cardShadow` Modifier에 `shape` 파라미터를 추가하여 그림자 모양을 카드 모양과 일치하도록 수정했습니다.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt`:
- Line 3: Remove the incorrect automatic import of
android.R.attr.contentDescription from PhotoDetailActionBar.kt; locate the
import statement "import android.R.attr.contentDescription" at the top of the
file and delete it so Compose Icon usages rely on the named contentDescription
parameter instead of this unrelated Android resource constant.
🧹 Nitpick comments (3)
feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt (3)
214-230: 빈 썸네일 상태에서hazeSource적용이 필요한지 확인하세요.빈 상태(
photoCount == 0 || thumbnailImage == null)에서 단색gray25배경에hazeSource(hazeState)를 적용하고 있습니다. 실제 이미지가 없는 상태에서 haze blur 소스로 사용하면AlbumFolder의 blur 효과가 단색 배경만 블러 처리하게 되어, 의도한 시각적 효과인지 확인이 필요합니다.
296-305:background와clip에 shape이 중복 지정되어 있습니다.Line 300에서
.clip(CircleShape)으로 이미 원형 클리핑이 적용되었고, Line 301-304에서.background(color = ..., shape = CircleShape)에도CircleShape이 다시 지정되어 있습니다.clip이후의background는 shape 파라미터 없이 사용해도 동일한 결과를 얻습니다.♻️ 중복 shape 제거
.clip(CircleShape) - .background( - color = NekiTheme.colorScheme.gray25.copy(alpha = 0.19f), - shape = CircleShape, - ) + .background(color = NekiTheme.colorScheme.gray25.copy(alpha = 0.19f))
207-213:ArchiveAlbumItem의 외부Box에 명시적 너비를 추가하세요.현재
Box에height만 설정되고width가 없습니다. 내부의 빈 상태 플레이스홀더와AsyncImage가matchParentSize()를 사용하므로, 이들의 크기는 부모Box의 constraints에 의존합니다.AlbumFolder가 별도로.width(ARCHIVE_ALBUM_ITEM_WIDTH.dp)를 설정하고 있어 현재 동작하지만, 외부Box에 명시적 너비를 추가하면 레이아웃 의도가 명확해지고 안정성이 향상됩니다.♻️ 너비 명시 제안
Box( modifier = modifier .cardShadow(shape = RoundedCornerShape(8.dp)) .height(ARCHIVE_ALBUM_ITEM_HEIGHT.dp) + .width(ARCHIVE_ALBUM_ITEM_WIDTH.dp) .clip(RoundedCornerShape(8.dp)) .noRippleClickable(onClick = onClick), ) {
`PhotoDetailActionBar.kt` 파일에서 사용되지 않는 `android.R.attr.contentDescription` import 문을 제거했습니다.
🔗 관련 이슈
📙 작업 설명
#112 에서 분기했습니다.
NekiTextField: 리팩터링 (textStyle,cursorBrush,lineLimits,titleLabel파라미터 추가),NekiTextFieldWithError추가ToolTipPopup:ArrowDirection,ToolTipColorenum 추가, 꼬리 방향/위치/정렬 커스텀 지원, 닫기 버튼 옵션ItemOverlay:PhotoGridItemOverlay→GridItemOverlay리네임,SelectedPhotoGridItemOverlay프리뷰 추가TopBar정리:TopBarTextButton제거,NekiTopBar정리NekiTextButton:enabledTextColor,disabledTextColor커스텀 색상 지원DoubleButtonOptionBottomSheet: 레이아웃 패딩/스페이싱 조정ActionBar:core:designsystem컴포넌트화dashStrokeModifier 추가📸 스크린샷 또는 시연 영상 (선택)
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항