[fix] #78 아카이브 업로드 QA 수정#84
Conversation
앨범 전체 보기 화면의 `LazyColumn`에서 `verticalArrangement` 속성을 제거하여 아이템 간의 간격을 조정했습니다.
Lottie 애니메이션을 표시하기 위해 `lottie-compose` 라이브러리 종속성을 추가하고, `loading_animation.json` 파일을 리소스로 추가했습니다.
기존 `CircularProgressIndicator`를 사용하던 로딩 인디케이터를 Lottie 애니메이션으로 교체했습니다.
네키 타이포 로고(Neki Typo Logo) 벡터 아이콘과 이를 사용하는 컴포저블 함수(`PrimaryNekiTypoLogo`, `GrayNekiTypoLogo`, `WhiteNekiTypoLogo`)를 추가했습니다.
아카이브 화면의 앨범 커버 디자인을 개선하고, `dev.chrisbanes.haze` 라이브러리를 사용해 블러 효과를 적용했습니다. - 앨범 커버를 `drawBehind` 대신 `Shape`으로 구현하여 재사용성을 높이고, `clip`을 사용하도록 변경 - `backgroundHazeBlur` Modifier 확장 함수를 추가하여 블러 효과를 공통으로 처리 - `AlbumFolderLayout`에 `Haze`를 적용하고, `RandomPoseTutorialOverlay`에도 이를 재사용하여 적용 - 앨범 커버 내 아이콘, 텍스트 스타일 및 레이아웃 등 전반적인 UI 디테일 수정 - `PhotoTitleRow` 파일명을 `ArchiveMainTitleRow`로 변경하여 명확성 개선
기존 `ArchiveMainTopBar` 내부에만 있던 툴팁 로직을, 디자인시스템의 범용 `ToolTipPopup` 컴포저블로 분리하고 이를 적용했습니다.
기존 `PhotoComponent`에 종속되어 있던 그라데이션 및 선택 효과 로직을 `PhotoGridItemOverlay`와 `SelectedPhotoGridItemOverlay` 컴포저블로 분리했습니다. 이를 통해 사진 그리드 아이템의 UI를 보다 독립적으로 관리하고 재사용성을 높였습니다. - `PhotoGridItemOverlay`, `SelectedPhotoGridItemOverlay` 컴포저블 추가 - `photoBackground` Modifier를 `photoGridBackground`로 변경하고, 그라데이션 효과를 상단에서 시작하도록 수정 - `PhotoComponent`에서 불필요한 `additionalContent` 파라미터 제거 - 아카이브 화면의 `SelectablePhotoItem`과 `ArchiveMainPhotoItem`에 새로운 오버레이 컴포저블 적용
Walkthrough로고 컴포넌트·벡터 리소스 추가, Lottie 로딩 도입, backgroundHazeBlur API 변경, 툴팁 팝업·빈 상태 벡터 추가, EXIF 회전 처리, 아카이브·업로드 UI 리팩토링 및 문자열·빌드 설정 업데이트가 포함됩니다. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
core/data/src/main/java/com/neki/android/core/data/remote/api/PhotoService.kt (1)
57-68:⚠️ Potential issue | 🟡 Minor주석과 함수 시그니처 간 불일치
주석이 "즐겨찾는 앨범 조회"로 변경되었으나, 함수명은
getFavoritePhotos, 반환 타입은FavoritePhotoResponse, 엔드포인트는/api/photos/favorite로 모두 "사진(Photo)" 기준입니다. 주석이 실제 동작과 맞지 않으면 혼동을 유발할 수 있으니, 주석 또는 함수명 중 하나를 통일하는 것이 좋겠습니다.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt (1)
1-1:⚠️ Potential issue | 🟡 Minor파일명 오타: "Selcetable" → "Selectable"
파일명이
SelcetablePhotoItem.kt으로 되어 있지만, 함수명은 올바르게SelectablePhotoItem입니다. 파일명을SelectablePhotoItem.kt으로 수정하면 일관성이 높아집니다.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTopBar.kt (1)
68-78:⚠️ Potential issue | 🟡 Minor
showAddPopup과showTooltip이 동시에true일 경우 팝업이 겹칠 수 있습니다.
AddPhotoPopup과ArchiveToolTip의 offset이 유사한 위치(각각 48dp, 47dp)이므로, 두 팝업이 동시에 표시되면 시각적으로 겹칩니다. 호출부에서 상태를 배타적으로 관리하거나, 여기서 가드를 추가하는 것을 권장합니다.🔧 가드 조건 추가 제안
if (showAddPopup) { AddPhotoPopup( onDismissRequest = onDismissPopup, onClickQRScan = onClickQRScan, onClickGallery = onClickGallery, onClickNewAlbum = onClickNewAlbum, ) } - if (showTooltip) { + if (showTooltip && !showAddPopup) { ArchiveToolTip() }
🤖 Fix all issues with AI agents
In
`@core/designsystem/src/main/java/com/neki/android/core/designsystem/logo/NekiTypoLogo.kt`:
- Around line 73-79: WhiteNekiTypoLogoPreview uses `@Preview` and renders a white
logo on the theme's white background so it's invisible; update the preview to
use the project's `@ComponentPreview` (for consistency) or change the preview to
render on a dark background (e.g., wrap WhiteNekiTypoLogo in a container/Surface
with a dark color or call NekiTheme with darkTheme=true) so the
WhiteNekiTypoLogo is visible; modify the WhiteNekiTypoLogoPreview function (and
its annotation) and ensure it still composes NekiTheme and calls
WhiteNekiTypoLogo.
In
`@core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Background.kt`:
- Around line 41-62: The Modifier.backgroundHazeBlur implementation ignores the
shape parameter when enabled is true, causing visual mismatch; update the
enabled=true branch to pass the shape into the hazeEffect (or otherwise apply
the provided Shape) by adding shape to the hazeEffect / HazeStyle call
(reference backgroundHazeBlur, hazeEffect, HazeStyle), and add KDoc `@param`
entries describing alpha and shape so both parameters are documented.
In
`@core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt`:
- Around line 25-41: The Popup currently cannot be dismissed by outside touch;
update ToolTipPopup to accept an onDismissRequest: () -> Unit parameter and pass
it to Popup as onDismissRequest, and set PopupProperties(focusable = true) (and
any other desired properties like dismissOnBackPress if needed) so outside taps
will dismiss; keep invoking ToolTipContent(tooltipText, color) as before. Use
the ToolTipPopup function name and Popup/PopupProperties symbols to locate where
to add the new parameter and attributes.
In `@core/designsystem/src/main/res/drawable/icon_empty_content.xml`:
- Around line 10-12: 아이콘 배경으로 하드코딩된 불투명 흰색 사각형(<path
android:pathData="M0,0h172v112h-172z" android:fillColor="#ffffff"/>)이 포함되어 있어
비흰색 배경에 배치 시 흰색 박스가 나타납니다; 이 <path> 요소를 삭제하거나 android:fillColor를 투명
값("#00000000")으로 변경하고, 아이콘이 투명 배경에서 의도대로 보이는지 (다른 패스들과 겹침/정렬 등) 확인하세요.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt`:
- Around line 152-159: AlbumDetailContent is declared public while
AlbumDetailScreen and AlbumDetailRoute are internal; make AlbumDetailContent
internal to unify visibility by changing its declaration from "fun
AlbumDetailContent(...)" (public) to "internal fun AlbumDetailContent(...)" and
update any internal callers if necessary so the function's visibility matches
AlbumDetailScreen and AlbumDetailRoute.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt`:
- Around line 58-68: The overlays use RoundedCornerShape(8.dp) which mismatches
PhotoComponent's RoundedCornerShape(12.dp) (and the selection border), causing
visual mismatch; update SelectedPhotoGridItemOverlay and PhotoGridItemOverlay
invocations in SelcetablePhotoItem (the block that checks isSelected) to use
RoundedCornerShape(12.dp) so the overlay corners align with PhotoComponent's
clipping and selection border.
- Around line 37-68: The selected state currently applies the same
semi-transparent black twice (once in PhotoComponent's modifier background and
again via SelectedPhotoGridItemOverlay); remove the background from
PhotoComponent when isSelected and only keep the darker visual from
SelectedPhotoGridItemOverlay so the dimming isn't doubled. Concretely, update
the conditional in the PhotoComponent modifier (around the isSelected branch) to
apply .border(...) and .clip(...) when isSelected but omit .background(...);
leave SelectedPhotoGridItemOverlay as-is to provide the selected dim overlay.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainPhotoItem.kt`:
- Around line 27-38: The overlay and photo clipping radii differ: PhotoComponent
clips its content with RoundedCornerShape(12.dp) while PhotoGridItemOverlay is
given RoundedCornerShape(8.dp), causing visual mismatch; fix by making them
consistent—either change the shape passed into PhotoGridItemOverlay to
RoundedCornerShape(12.dp) or apply the same clip(RoundedCornerShape(12.dp)) to
the outer Box that wraps PhotoComponent and PhotoGridItemOverlay so both use the
identical 12.dp radius (refer to PhotoComponent, PhotoGridItemOverlay and the
surrounding Box in ArchiveMainPhotoItem).
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTopBar.kt`:
- Around line 94-107: The tooltip Popup cannot be dismissed by external touch or
back button because ToolTipPopup is used without onDismissRequest/focusable
handling; update either ToolTipPopup to accept and pass an onDismissRequest
lambda and set focusable = true on its Popup (so external taps/back press call
the callback), or implement a timed dismiss inside ArchiveToolTip that calls a
provided dismiss callback after N milliseconds; refer to ToolTipPopup,
ArchiveToolTip and AddPhotoPopup for the pattern (use onDismissRequest and
focusable = true as in AddPhotoPopup) and wire an onDismissRequest up to the
caller so the tooltip can be dismissed.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt`:
- Around line 24-66: AllPhotoEmptyContent is dead/duplicated UI (duplicate of
EmptyContent) and its AllPhotoTopBar has no-op handlers; remove the
AllPhotoEmptyContent composable and any associated preview functions, update any
local references to use EmptyContent instead (e.g., where AllPhotoEmptyContent
was referenced in previews or tests), and delete unused imports; also remove the
unused empty lambda handlers tied to AllPhotoTopBar within the removed code.
🧹 Nitpick comments (10)
feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt (4)
100-105:derivedStateOf사용이 적절하나,isEmpty판정 시 초기 로딩 상태 고려 필요.
pagingItems.loadState.refresh is LoadState.NotLoading조건으로 초기 로딩 중 빈 화면이 잠깐 보이는 문제를 방지한 점이 좋습니다.다만,
remember블록이 키 없이 사용되고 있어pagingItems객체가 교체되는 경우(예: Configuration change 이외의 상위 recomposition으로 새LazyPagingItems인스턴스가 전달되는 경우)에는 stale 참조가 남을 수 있습니다. 현재 구조에서는collectAsLazyPagingItems()가 내부적으로 snapshot state를 사용하므로 문제가 없을 것으로 보이지만, 방어적으로 키를 추가하는 것을 고려해 볼 수 있습니다.♻️ 방어적 키 추가 제안
- val isRefreshing by remember { + val isRefreshing by remember(pagingItems) { derivedStateOf { pagingItems.loadState.refresh is LoadState.Loading } } - val isEmpty by remember { + val isEmpty by remember(pagingItems) { derivedStateOf { pagingItems.itemCount == 0 && pagingItems.loadState.refresh is LoadState.NotLoading } }
111-115: 타이틀 로직이 두 곳에서 중복됩니다.
if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title로직이 Line 113 (EmptyContent)과 Line 166 (AlbumDetailTopBar) 양쪽에 동일하게 존재합니다. 향후 타이틀 규칙 변경 시 한 쪽만 수정하는 실수를 방지하기 위해,AlbumDetailScreen레벨에서 한 번만 계산하여 내려주는 것을 권장합니다.♻️ 타이틀 로직 통합 제안
AlbumDetailScreen내에서 타이틀을 미리 계산:+ val displayTitle = if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title + if (isEmpty) { EmptyContent( - title = if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title, + title = displayTitle, onClickBack = { onIntent(AlbumDetailIntent.ClickBackIcon) }, ) } else {그리고
AlbumDetailContent에title파라미터를 추가하거나, 내부에서도 동일하게 전달된 값을 사용하도록 수정합니다.Also applies to: 165-166
125-127:LoadingDialog가 dismiss 불가능한 상태로 표시됩니다.
LoadingDialog()가 기본 파라미터로 호출되고 있으며, 외부 컨텍스트에서onDismissRequest = {}가 기본값입니다.isRefreshing이 네트워크 지연 등으로 오래 유지될 경우 사용자가 dismiss할 수 없어 UI가 블로킹됩니다. 의도된 동작이라면 무시하셔도 됩니다.
234-257: 프리뷰에서@Preview대신@ComponentPreview사용을 고려해 주세요.
AlbumDetailTopBar.kt에서는@ComponentPreview를 사용하고 있지만, 이 파일에서는@Preview를 직접 사용하고 있습니다. 프로젝트 내 프리뷰 어노테이션 사용이 일관되지 않습니다.core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt (1)
43-50:album.title을 무시하고 하드코딩된 문자열 사용
FavoriteAlbumRowComponent는AlbumPreview객체를 받지만,album.title을 사용하지 않고"즐겨찾기"를 하드코딩하고 있습니다. 이렇게 하면 호출부에서 전달한title값이 무시되어 혼동을 줄 수 있습니다.의도적으로 고정 타이틀을 사용하는 경우라면, 파라미터에서
AlbumPreview대신 필요한 필드(thumbnailUrl,photoCount)만 받거나, 명시적으로 상수를 사용하는 방식을 고려해 보세요.♻️ 제안: 필요한 필드만 파라미터로 받기
`@Composable` fun FavoriteAlbumRowComponent( - album: AlbumPreview, + thumbnailUrl: String?, + photoCount: Int, modifier: Modifier = Modifier, onClick: () -> Unit = {}, ) { Row( modifier = modifier .noRippleClickable(onClick = onClick) .fillMaxWidth() .padding(vertical = 10.dp, horizontal = 20.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), ) { FavoriteAlbumThumbnail( - thumbnailUrl = album.thumbnailUrl, + thumbnailUrl = thumbnailUrl, ) AlbumInfo( title = "즐겨찾기", - photoCount = album.photoCount, + photoCount = photoCount, ) } }core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt (1)
52-83: 꼬리(tail) 위치가 오른쪽 정렬로 하드코딩되어 있습니다.현재 꼬리가
padding(end = 16.dp)+Alignment.CenterEnd로 항상 오른쪽에 고정됩니다. 공통 컴포넌트로서 다양한 방향에서 사용하려면, 꼬리 정렬을 파라미터로 받는 것을 고려해 보세요.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt (1)
196-208:AlbumFolder의hazeState기본값이 블러 소스 없이 생성됩니다.
AlbumFolder는private함수이고, 실제로는ArchiveAlbumItem에서 항상 명시적으로hazeState를 전달하므로 현재 문제는 없습니다. 다만rememberHazeState()기본값은 소스가 연결되지 않은 상태라 블러가 동작하지 않으므로, 혼동 방지를 위해 기본값을 제거하는 것도 고려해 볼 수 있습니다.AlbumFolderLayout(Line 266)에도 동일하게 적용됩니다.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyContent.kt (1)
59-70:EmptyTopBar는BackTitleTopBar의 단순 래퍼입니다.현재 추가 로직 없이 파라미터를 전달만 하고 있으므로,
EmptyContent내에서BackTitleTopBar를 직접 호출해도 무방합니다. 다만 private 함수이고 향후 확장 가능성을 고려하면 현재 상태도 수용 가능합니다.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoScreen.kt (1)
260-266: 빈 프리뷰 함수는 제거하거나 구체화하는 것이 좋습니다.현재
AllPhotoScreenPreview는 주석만 포함되어 있고 실제 UI를 렌더링하지 않습니다. Paging을 프리뷰에서 사용하기 어려운 점은 이해하지만, 빈 프리뷰는 유지 보수 가치가 낮으므로 제거하거나,flowOf(PagingData.from(sampleList)).collectAsLazyPagingItems()와 같은 패턴으로 간단한 샘플 데이터를 제공하는 것을 고려해 보세요.core/ui/src/main/java/com/neki/android/core/ui/component/LoadingIndicator.kt (1)
20-35:LoadingDialog에서modifier.size(size)로 전달 시.size()가 중복 적용됩니다.
LoadingDialog가modifier.size(200.dp)를LoadingIndicator에 전달하면,LoadingIndicator내부에서 다시.size(80.dp)가 체이닝됩니다. Compose에서는 첫 번째.size()가 우선하므로 현재는 200dp로 렌더링되어 동작상 문제는 없지만, 의도가 불명확하고 유지보수 시 혼동을 줄 수 있습니다.
LoadingDialog에서는size값을LoadingIndicator의size파라미터로 전달하는 것이 명확합니다.♻️ 수정 제안
fun LoadingDialog( modifier: Modifier = Modifier, size: Dp = 200.dp, properties: DialogProperties = DialogProperties(), onDismissRequest: () -> Unit = {}, ) { Dialog( onDismissRequest = onDismissRequest, properties = properties, ) { LoadingIndicator( - modifier = modifier.size(size), + modifier = modifier, + size = size, ) } }
포토 그리드 아이템의 배경에 `Color.Black.copy(alpha = 0.04f)` 속성의 배경을 추가하여, 기존 그래디언트 배경 위에 새로운 레이어를 더했습니다.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt`:
- Around line 237-244: The preview date string for val photos in
AlbumDetailScreen (the Photo objects created in the photos map) can become
invalid for it >= 10 (e.g., "2024-04-210"); change the date generation to
produce a valid day component (for example compute an integer day and
interpolate it) such as using "2024-04-${20 + it}" or formatting a zero-padded
day with String.format("%02d", day) so all Photo.date values are valid ISO-like
dates and won't crash date-parsing logic.
In
`@feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt`:
- Line 1: Rename the file from SelcetablePhotoItem.kt to SelectablePhotoItem.kt
so the filename matches the existing symbol SelectablePhotoItem; update any
references/imports that still point to the old filename (e.g., import
statements, module registrations or tests) to use SelectablePhotoItem, and
ensure your VCS commit preserves the rename (git mv) to avoid duplicate class
files.
🧹 Nitpick comments (7)
core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Background.kt (1)
26-34: 그라데이션 stop에 매직 넘버134f / 242f사용.
134f / 242f는 특정 디자인 스펙(컴포넌트 높이 242px 중 134px 지점)에서 유래한 것으로 보입니다. 컴포넌트 높이가 변경되면 의도와 달라질 수 있으므로, 의미를 명확히 하는 상수나 주석을 추가하면 유지보수에 도움이 됩니다.예시
+// 디자인 스펙: 242px 높이 기준 134px 지점에서 그라데이션 종료 +private const val GRADIENT_END_FRACTION = 134f / 242f + fun Modifier.photoGridBackground( shape: Shape = RoundedCornerShape(8.dp), ): Modifier = this .background( color = Color.Black.copy(alpha = 0.04f), shape = shape, ) .background( brush = Brush.verticalGradient( colorStops = arrayOf( 0f to Color.Black.copy(alpha = 0.2f), - 134f / 242f to Color.Black.copy(alpha = 0f), + GRADIENT_END_FRACTION to Color.Black.copy(alpha = 0f), ), ), shape = shape, )core/common/src/main/java/com/neki/android/core/common/util/ByteArray.kt (1)
21-36: 대용량 이미지에서 OOM 위험이 있습니다.현재 전체 바이트 배열(
bytes) + 디코딩된 비트맵(bitmap) + 회전된 비트맵(rotatedBitmap) + 출력 스트림이 동시에 메모리에 존재합니다. 고해상도 이미지의 경우 약 3~4배의 메모리를 소비하여 OOM이 발생할 수 있습니다.
BitmapFactory.Options의inSampleSize를 활용하거나 최대 해상도를 제한하는 방식을 고려해 보세요.♻️ 예시: inSampleSize를 활용한 메모리 최적화
+private const val MAX_DIMENSION = 4096 + fun Uri.toByteArray( context: Context, quality: Int = DEFAULT_QUALITY, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, ): ByteArray? { val orientation = context.contentResolver.openInputStream(this)?.use { input -> ExifInterface(input).getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED, ) } ?: ExifInterface.ORIENTATION_UNDEFINED val bytes = context.contentResolver.openInputStream(this)?.use { it.readBytes() } ?: return null - val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return null + val options = BitmapFactory.Options().apply { + inJustDecodeBounds = true + BitmapFactory.decodeByteArray(bytes, 0, bytes.size, this) + inSampleSize = calculateInSampleSize(this, MAX_DIMENSION, MAX_DIMENSION) + inJustDecodeBounds = false + } + val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options) ?: return null val rotatedBitmap = bitmap.applyOrientation(orientation)feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt (2)
100-105:remember에pagingItems키를 추가하는 것을 권장합니다.
derivedStateOf클로저가pagingItems참조를 캡처하고 있지만,remember에 키가 없으므로pagingItems인스턴스가 변경되면(예: 구성 변경 또는 navigation 재진입 시) 이전 인스턴스를 계속 참조하게 됩니다.실제로
collectAsLazyPagingItems()가 안정적인 참조를 반환하는 경우가 많지만, 안전한 패턴을 위해 키를 명시하는 것이 좋습니다.♻️ 수정 제안
- val isRefreshing by remember { + val isRefreshing by remember(pagingItems) { derivedStateOf { pagingItems.loadState.refresh is LoadState.Loading } } - val isEmpty by remember { + val isEmpty by remember(pagingItems) { derivedStateOf { pagingItems.itemCount == 0 && pagingItems.loadState.refresh is LoadState.NotLoading } }
111-123: 타이틀 로직이 중복되어 있습니다.Line 113과 Line 166에서
if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title동일 로직이 반복됩니다. 한쪽만 수정 시 불일치가 발생할 수 있으므로, 상위에서 한 번만 계산하여 전달하거나AlbumDetailState의 프로퍼티로 추출하는 것을 권장합니다.♻️ 예시: AlbumDetailScreen에서 로컬 변수로 추출
+ val displayTitle = if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title + if (isEmpty) { EmptyContent( - title = if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title, + title = displayTitle, onClickBack = { onIntent(AlbumDetailIntent.ClickBackIcon) }, ) } else {그리고
AlbumDetailContent에도displayTitle을 파라미터로 전달하거나, 동일하게 적용합니다.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainContract.kt (1)
18-18:isShowAddDialog네이밍이 리네이밍된 intent와 불일치합니다.Intent가
DismissAddDialog→DismissAddPopup으로 변경되었지만, 상태 필드는 여전히isShowAddDialog로 남아 있습니다. 일관성을 위해isShowAddPopup으로 변경하는 것을 고려해 주세요.feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt (2)
47-47:isFirstEntered = false설정이 중복됩니다.Line 47에서
EnterArchiveMainScreen을 제외한 모든 intent에 대해isFirstEntered = false를 설정하고 있어, Line 58의ClickScreen핸들러와 Line 64의DismissToolTipPopup핸들러에서 동일한 reduce가 불필요하게 반복됩니다.의도가 "어떤 인터랙션이든 툴팁을 닫는다"라면 Line 47의 blanket 처리로 충분하며,
DismissToolTipPopupintent 자체가 필요 없을 수 있습니다. 반대로 특정 액션에서만 툴팁을 닫으려는 의도라면 Line 47의 blanket 처리를 제거하고 개별 핸들러에서 명시적으로 관리하는 것이 더 명확합니다.현재 구조에서는
DismissToolTipPopup의 역할이 모호합니다.Also applies to: 58-58, 63-64
122-137:fetchInitialData의 에러 처리 확인.
awaitAll내부의 개별 fetch 함수(fetchFavoriteSummary,fetchPhotos,fetchFolders)가 각각onFailure에서 로깅만 수행하므로, 하나가 실패해도 다른 요청은 정상 진행되고isLoading = false로 전환됩니다. 현재 구조상 사용자에게 에러 피드백이 전달되지 않는 점은 인지하고 계신지 확인 부탁드립니다.
`offset`으로 구현되어 있던 버튼의 패딩을 `contentPadding`을 사용하도록 변경하여 터치 영역을 확장했습니다.
`build.gradle.kts` 파일에 정의된 일부 BuildConfig 필드명의 오타를 수정합니다. - `PHOTOISM_IMG_URL_MIME_TYPE` → `PHOTOISM_IMAGE_URL_MIME_TYPE` - `LIFE_FOUR_CUT_URL_MIME_TYPE` → `LIFE_FOUR_CUT_IMAGE_URL_MIME_TYPE` 수정된 필드명을 참조하도록 `PhotoWebViewClient.kt` 파일도 함께 변경했습니다.
Ojongseok
left a comment
There was a problem hiding this comment.
Q. 이미지 백그라운드 설정을 Overlay 형식으로 했습니다. 이 부분 괜찮을까요?
Modifier.xxxBackground()가 아닌 별도로 컴포저블 컴포넌트 분리하여 사용하는 해당 PR에서의 형식 말씀하시는게 맞을까요? -> 맞다면 저는 해당 방식처럼 :core:ui나 :feature:component에 별도 컴포넌트로 사용하는게 좀 더 편한 것 같습니다.
`AlbumDetailTopBar` 컴포저블의 `onClickBack`, `onClickSelect`, `onClickCancel` 파라미터에 기본으로 빈 람다 `{}`를 할당했습니다. 이를 통해 호출부에서 불필요한 빈 콜백을 매번 전달해야 하는 코드를 간소화했습니다.
`SelcetablePhotoItem`의 파일명을 `SelectablePhotoItem`으로 수정하고, 선택된 사진 아이템의 테두리 radius 값을 12dp에서 8dp로 변경했습니다.
`photoGridBackground` Modifier 확장 함수를 제거하고, `ItemOverlay` 컴포저블 내부에서 `Modifier.background`를 직접 사용하도록 수정했습니다.
기존 Modifier.xxxBackground() 확장함수를 오버레이에서만 사용하고 있어서 확장함수를 제거하고 Overlay 컴포넌트로 유지해도 괜찮을 것 같습니다! |
포즈피드 랜덤 아이템 UI에 그라데이션 배경과 흐림(blur) 효과를 적용했습니다.
🔗 관련 이슈
📙 작업 설명
EmptyContent공통화ToolTipPopup공통 컴포저블 추가 및 dismiss 분리PhotoComponentcornerRadius 8dp로 수정SelectablePhotoItem선택 시 검은 배경 오버레이 제거AlbumDetailContent접근제어자 internal로 변경backgroundHazeBlur미사용 파라미터 제거 및 KDoc 업데이트📷 스크린샷
Summary by CodeRabbit
New Features
Style
Refactor