From bc29dd2c049addced5ec21639a074a1493e2eab2 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 14 Feb 2026 23:22:12 +0900 Subject: [PATCH 01/30] =?UTF-8?q?[refactor]=20#108:=20=EB=9E=9C=EB=8D=A4?= =?UTF-8?q?=20=ED=8F=AC=EC=A6=88=20=ED=94=8C=EB=A1=9C=ED=8C=85=EB=B0=94=20?= =?UTF-8?q?=EB=B0=B0=EA=B2=BD=20=ED=88=AC=EB=AA=85=EB=8F=84=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/random/component/RandomPoseFloatingBar.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseFloatingBar.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseFloatingBar.kt index 824f44374..0b560a094 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseFloatingBar.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseFloatingBar.kt @@ -22,9 +22,9 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.R import com.neki.android.core.designsystem.button.NekiIconButton import com.neki.android.core.designsystem.ui.theme.NekiTheme -import com.neki.android.core.designsystem.R @Composable internal fun RandomPoseFloatingBarContent( @@ -82,10 +82,6 @@ private fun RandomPoseFloatingBar( modifier = modifier .fillMaxWidth() .clip(CircleShape) - .background( - color = NekiTheme.colorScheme.white.copy(alpha = 0.6f), - shape = CircleShape, - ) .border( width = 1.dp, brush = Brush.verticalGradient( @@ -96,6 +92,10 @@ private fun RandomPoseFloatingBar( ), shape = CircleShape, ) + .background( + color = NekiTheme.colorScheme.white.copy(alpha = 0.3f), + shape = CircleShape, + ) .padding(8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, From 83ae494fc2defb53a435bacc79bcbef3fb5c489b Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:01:11 +0900 Subject: [PATCH 02/30] =?UTF-8?q?[design]=20#108:=20Action=20Bar=20core:de?= =?UTF-8?q?signsystem=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/actionbar/NekiActionBar.kt | 165 ++++++++++++++++++ .../impl/photo/component/PhotoActionBar.kt | 49 +++--- .../impl/photo_detail/PhotoDetailScreen.kt | 10 +- .../component/PhotoDetailActionBar.kt | 82 ++++----- .../pose/impl/detail/PoseDetailScreen.kt | 27 +-- .../impl/detail/component/PoseActionBar.kt | 59 +++++++ 6 files changed, 291 insertions(+), 101 deletions(-) create mode 100644 core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt create mode 100644 feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/component/PoseActionBar.kt diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt new file mode 100644 index 000000000..124eefa16 --- /dev/null +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt @@ -0,0 +1,165 @@ +package com.neki.android.core.designsystem.actionbar + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.R +import com.neki.android.core.designsystem.button.NekiIconButton +import com.neki.android.core.designsystem.ui.theme.NekiTheme + +@Composable +fun NekiStartActionBar( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Column( + modifier = modifier, + ) { + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = NekiTheme.colorScheme.gray75, + ) + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.CenterStart, + ) { + content() + } + } +} + +@Composable +fun NekiEndActionBar( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Column( + modifier = modifier, + ) { + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = NekiTheme.colorScheme.gray75, + ) + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.CenterEnd, + ) { + content() + } + } +} + +@Composable +fun NekiBothSidesActionBar( + modifier: Modifier = Modifier, + startContent: @Composable () -> Unit, + endContent: @Composable () -> Unit, +) { + Column( + modifier = modifier, + ) { + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = NekiTheme.colorScheme.gray75, + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + startContent() + endContent() + } + } +} + +@ComponentPreview +@Composable +private fun NekiStartActionBarPreview() { + NekiTheme { + NekiStartActionBar( + modifier = Modifier.fillMaxWidth(), + ) { + NekiIconButton( + modifier = Modifier.padding(8.dp), + onClick = {}, + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.icon_arrow_left), + contentDescription = null, + tint = NekiTheme.colorScheme.gray900, + ) + } + } + } +} + +@ComponentPreview +@Composable +private fun NekiEndActionBarPreview() { + NekiTheme { + NekiEndActionBar( + modifier = Modifier.fillMaxWidth(), + ) { + NekiIconButton( + modifier = Modifier.padding(8.dp), + onClick = {}, + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.icon_scrap_stroked), + contentDescription = null, + tint = NekiTheme.colorScheme.gray500, + ) + } + } + } +} + +@ComponentPreview +@Composable +private fun NekiBothSidesActionBarPreview() { + NekiTheme { + NekiBothSidesActionBar( + modifier = Modifier.fillMaxWidth(), + startContent = { + NekiIconButton( + modifier = Modifier.padding(8.dp), + onClick = {}, + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.icon_arrow_left), + contentDescription = null, + tint = NekiTheme.colorScheme.gray900, + ) + } + }, + endContent = { + NekiIconButton( + modifier = Modifier.padding(8.dp), + onClick = {}, + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.icon_scrap_stroked), + contentDescription = null, + tint = NekiTheme.colorScheme.gray500, + ) + } + }, + ) + } +} diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/PhotoActionBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/PhotoActionBar.kt index f63120a05..1f8e2e518 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/PhotoActionBar.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/PhotoActionBar.kt @@ -3,12 +3,8 @@ package com.neki.android.feature.archive.impl.photo.component import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -18,6 +14,7 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.R +import com.neki.android.core.designsystem.actionbar.NekiBothSidesActionBar import com.neki.android.core.designsystem.modifier.noRippleClickableSingle import com.neki.android.core.designsystem.ui.theme.NekiTheme @@ -35,41 +32,35 @@ internal fun PhotoActionBar( enter = expandVertically(expandFrom = Alignment.Top), exit = shrinkVertically(shrinkTowards = Alignment.Top), ) { - Column( - modifier = modifier.fillMaxWidth(), - ) { - HorizontalDivider( - modifier = Modifier.fillMaxWidth(), - thickness = 1.dp, - color = NekiTheme.colorScheme.gray75, - ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { + NekiBothSidesActionBar( + modifier = Modifier.fillMaxWidth(), + startContent = { Icon( - modifier = Modifier.noRippleClickableSingle( - enabled = isEnabled, - onClick = onClickDownload, - ), + modifier = Modifier + .padding(20.dp) + .noRippleClickableSingle( + enabled = isEnabled, + onClick = onClickDownload, + ), imageVector = ImageVector.vectorResource(R.drawable.icon_download), contentDescription = null, tint = if (isEnabled) NekiTheme.colorScheme.gray600 else NekiTheme.colorScheme.gray200, ) + }, + endContent = { Icon( - modifier = Modifier.noRippleClickableSingle( - enabled = isEnabled, - onClick = onClickDelete, - ), + modifier = Modifier + .padding(20.dp) + .noRippleClickableSingle( + enabled = isEnabled, + onClick = onClickDelete, + ), imageVector = ImageVector.vectorResource(R.drawable.icon_trash), contentDescription = null, tint = if (isEnabled) NekiTheme.colorScheme.gray600 else NekiTheme.colorScheme.gray200, ) - } - } + }, + ) } } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.kt index f3256d625..04bbf638e 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/PhotoDetailScreen.kt @@ -4,22 +4,20 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import com.neki.android.core.designsystem.DevicePreview import com.neki.android.core.designsystem.topbar.BackTitleTopBar import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.core.model.Photo -import com.neki.android.core.ui.component.LoadingDialog import com.neki.android.core.navigation.result.LocalResultEventBus +import com.neki.android.core.ui.component.LoadingDialog import com.neki.android.core.ui.compose.collectWithLifecycle import com.neki.android.core.ui.toast.NekiToast import com.neki.android.feature.archive.impl.component.DeletePhotoDialog @@ -83,12 +81,6 @@ internal fun PhotoDetailScreen( contentScale = ContentScale.Fit, ) - HorizontalDivider( - modifier = Modifier.fillMaxWidth(), - thickness = 1.dp, - color = NekiTheme.colorScheme.gray75, - ) - PhotoDetailActionBar( isFavorite = uiState.photo.isFavorite, onClickDownload = { onIntent(PhotoDetailIntent.ClickDownloadIcon) }, diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt index 34cea8844..170d80342 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt @@ -7,13 +7,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.R +import com.neki.android.core.designsystem.actionbar.NekiBothSidesActionBar import com.neki.android.core.designsystem.modifier.noRippleClickable import com.neki.android.core.designsystem.ui.theme.NekiTheme @@ -25,52 +25,52 @@ internal fun PhotoDetailActionBar( onClickFavorite: () -> Unit = {}, onClickDelete: () -> Unit = {}, ) { - Row( - modifier = modifier - .fillMaxWidth() - .padding(20.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Icon( - modifier = Modifier - .size(28.dp) - .noRippleClickable { onClickDownload() }, - imageVector = ImageVector.vectorResource(R.drawable.icon_download), - contentDescription = null, - tint = NekiTheme.colorScheme.gray500, - ) + NekiBothSidesActionBar( + modifier = modifier.fillMaxWidth(), + startContent = { + Row( + modifier = Modifier.padding(20.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon( + modifier = Modifier + .size(28.dp) + .noRippleClickable { onClickDownload() }, + imageVector = ImageVector.vectorResource(R.drawable.icon_download), + contentDescription = null, + tint = NekiTheme.colorScheme.gray500, + ) + Icon( + modifier = Modifier + .size(28.dp) + .noRippleClickable { onClickFavorite() }, + imageVector = if (isFavorite) { + ImageVector.vectorResource(R.drawable.icon_heart_filled) + } else { + ImageVector.vectorResource(R.drawable.icon_heart_stroked) + }, + contentDescription = null, + tint = if (isFavorite) { + NekiTheme.colorScheme.primary400 + } else { + NekiTheme.colorScheme.gray700 + }, + ) + } + }, + endContent = { Icon( modifier = Modifier + .padding(20.dp) .size(28.dp) - .noRippleClickable { onClickFavorite() }, - imageVector = if (isFavorite) { - ImageVector.vectorResource(R.drawable.icon_heart_filled) - } else { - ImageVector.vectorResource(R.drawable.icon_heart_stroked) - }, + .noRippleClickable { onClickDelete() }, + imageVector = ImageVector.vectorResource(R.drawable.icon_trash), contentDescription = null, - tint = if (isFavorite) { - NekiTheme.colorScheme.primary400 - } else { - NekiTheme.colorScheme.gray700 - }, + tint = NekiTheme.colorScheme.gray600, ) - } - - Icon( - modifier = Modifier - .size(28.dp) - .noRippleClickable { onClickDelete() }, - imageVector = ImageVector.vectorResource(R.drawable.icon_trash), - contentDescription = null, - tint = NekiTheme.colorScheme.gray600, - ) - } + }, + ) } @ComponentPreview diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailScreen.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailScreen.kt index 113c37264..f7a6f8d84 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailScreen.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/PoseDetailScreen.kt @@ -3,23 +3,18 @@ package com.neki.android.feature.pose.impl.detail import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import com.neki.android.core.designsystem.DevicePreview -import com.neki.android.core.designsystem.button.NekiIconButton import com.neki.android.core.designsystem.topbar.BackTitleTopBar import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.core.model.Pose @@ -27,7 +22,7 @@ import com.neki.android.core.navigation.result.LocalResultEventBus import com.neki.android.core.ui.compose.collectWithLifecycle import com.neki.android.core.ui.toast.NekiToast import com.neki.android.feature.pose.api.PoseResult -import com.neki.android.core.designsystem.R +import com.neki.android.feature.pose.impl.detail.component.PoseActionBar @Composable internal fun PoseDetailRoute( @@ -87,22 +82,10 @@ internal fun PoseDetailScreen( thickness = 1.dp, color = NekiTheme.colorScheme.gray75, ) - NekiIconButton( - modifier = Modifier - .align(Alignment.End) - .padding(8.dp), - onClick = { onIntent(PoseDetailIntent.ClickScrapIcon) }, - ) { - Icon( - imageVector = ImageVector.vectorResource( - if (uiState.pose.isScrapped) R.drawable.icon_scrap_filled - else R.drawable.icon_scrap_stroked, - ), - contentDescription = null, - tint = if (uiState.pose.isScrapped) NekiTheme.colorScheme.gray900 - else NekiTheme.colorScheme.gray500, - ) - } + PoseActionBar( + isScrapped = uiState.pose.isScrapped, + onClickScrap = { onIntent(PoseDetailIntent.ClickScrapIcon) }, + ) } } diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/component/PoseActionBar.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/component/PoseActionBar.kt new file mode 100644 index 000000000..9c0b73011 --- /dev/null +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/detail/component/PoseActionBar.kt @@ -0,0 +1,59 @@ +package com.neki.android.feature.pose.impl.detail.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.R +import com.neki.android.core.designsystem.actionbar.NekiEndActionBar +import com.neki.android.core.designsystem.button.NekiIconButton +import com.neki.android.core.designsystem.ui.theme.NekiTheme + +@Composable +internal fun PoseActionBar( + isScrapped: Boolean, + modifier: Modifier = Modifier, + onClickScrap: () -> Unit = {}, +) { + NekiEndActionBar( + modifier = modifier.fillMaxWidth(), + ) { + NekiIconButton( + modifier = Modifier.padding(8.dp), + onClick = onClickScrap, + ) { + Icon( + modifier = Modifier.size(28.dp), + imageVector = ImageVector.vectorResource( + if (isScrapped) R.drawable.icon_scrap_filled + else R.drawable.icon_scrap_stroked, + ), + contentDescription = null, + tint = if (isScrapped) NekiTheme.colorScheme.gray900 + else NekiTheme.colorScheme.gray500, + ) + } + } +} + +@ComponentPreview +@Composable +private fun ScrappedPoseActionBarPreview() { + NekiTheme { + PoseActionBar(isScrapped = true) + } +} + +@ComponentPreview +@Composable +private fun UnScrappedPoseActionBarPreview() { + NekiTheme { + PoseActionBar(isScrapped = false) + } +} From bbbfdf2385e78040cf3e3ae1a6f31d25055fb315 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:03:28 +0900 Subject: [PATCH 03/30] =?UTF-8?q?[design]=20#108:=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EC=B9=A9=20=EB=AA=A8=EC=96=91=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `FilterBar`, `DownIconFilterChip`, `DefaultFilterChip` 컴포저블에 `chipShape` 파라미터를 추가하여 필터칩의 모양을 외부에서 설정할 수 있도록 기능을 확장했습니다. --- .../com/neki/android/core/ui/component/FilterBar.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/FilterBar.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/FilterBar.kt index a36067187..686333958 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/FilterBar.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/FilterBar.kt @@ -15,6 +15,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp @@ -31,6 +32,7 @@ fun FilterBar( defaultChipDisplayText: String, modifier: Modifier = Modifier, visible: Boolean = true, + chipShape: Shape = CircleShape, onClickDownIconChip: () -> Unit = {}, onClickDefaultChip: () -> Unit = {}, ) { @@ -50,11 +52,13 @@ fun FilterBar( DownIconFilterChip( isSelected = isDownIconChipSelected, displayText = downIconChipDisplayText, + chipShape = chipShape, onClick = onClickDownIconChip, ) DefaultFilterChip( isSelected = isDefaultChipSelected, displayText = defaultChipDisplayText, + chipShape = chipShape, onClick = onClickDefaultChip, ) } @@ -66,12 +70,13 @@ private fun DownIconFilterChip( isSelected: Boolean, displayText: String, modifier: Modifier = Modifier, + chipShape: Shape = CircleShape, onClick: () -> Unit = {}, ) { Row( modifier = modifier .background( - shape = CircleShape, + shape = chipShape, color = if (isSelected) NekiTheme.colorScheme.gray800 else NekiTheme.colorScheme.gray50, ) @@ -100,12 +105,13 @@ private fun DefaultFilterChip( isSelected: Boolean, displayText: String, modifier: Modifier = Modifier, + chipShape: Shape = CircleShape, onClick: () -> Unit = {}, ) { Text( modifier = modifier .background( - shape = CircleShape, + shape = chipShape, color = if (isSelected) NekiTheme.colorScheme.gray800 else NekiTheme.colorScheme.gray50, ) From 4fda9a37901ec14512ef9c2a87484689125a19fe Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:03:29 +0900 Subject: [PATCH 04/30] =?UTF-8?q?[design]=20#108:=20NekiTextField=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20=EB=B0=8F=20NekiTextFiel?= =?UTF-8?q?dWithError=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - titleLabel, textStyle, cursorBrush, lineLimits 파라미터 추가 - NekiTextFieldWithError 컴포넌트 추가 (에러 메시지 영역 포함) - NekiTextFieldWithLabel 제거, titleLabel 파라미터로 통합 - 프리뷰 4개 추가 --- .../core/designsystem/NekiTextField.kt | 279 ++++++++++++++++++ .../bottomsheet/NekiTextFieldBottomSheet.kt | 97 +----- .../feature/mypage/impl/const/MyPageConst.kt | 5 + .../mypage/impl/profile/EditProfileScreen.kt | 70 +---- 4 files changed, 296 insertions(+), 155 deletions(-) create mode 100644 core/designsystem/src/main/java/com/neki/android/core/designsystem/NekiTextField.kt create mode 100644 feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/const/MyPageConst.kt diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/NekiTextField.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/NekiTextField.kt new file mode 100644 index 000000000..f897dd6dd --- /dev/null +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/NekiTextField.kt @@ -0,0 +1,279 @@ +package com.neki.android.core.designsystem + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation +import androidx.compose.foundation.text.input.OutputTransformation +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.neki.android.core.designsystem.ui.theme.NekiTheme + +@Composable +fun NekiTextField( + textFieldState: TextFieldState, + modifier: Modifier = Modifier, + titleLabel: String? = null, + placeholder: String = "", + maxLength: Int? = null, + isError: Boolean = false, + textStyle: TextStyle = NekiTheme.typography.body16Medium.copy( + color = NekiTheme.colorScheme.gray900, + ), + cursorBrush: Brush = SolidColor(NekiTheme.colorScheme.gray800), + lineLimits: TextFieldLineLimits = TextFieldLineLimits.SingleLine, + inputTransformation: InputTransformation? = null, + outputTransformation: OutputTransformation? = null, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused by interactionSource.collectIsFocusedAsState() + + val borderColor = when { + isError -> NekiTheme.colorScheme.primary600 + isFocused -> NekiTheme.colorScheme.gray700 + else -> NekiTheme.colorScheme.gray75 + } + + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + titleLabel?.let { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 2.dp), + text = it, + style = NekiTheme.typography.body14Medium, + color = NekiTheme.colorScheme.gray700, + ) + } + + BasicTextField( + state = textFieldState, + modifier = Modifier + .fillMaxWidth() + .background( + color = NekiTheme.colorScheme.white, + shape = RoundedCornerShape(8.dp), + ) + .border( + width = 1.dp, + color = borderColor, + shape = RoundedCornerShape(8.dp), + ) + .padding(horizontal = 16.dp, vertical = 13.dp), + textStyle = textStyle, + inputTransformation = inputTransformation, + outputTransformation = outputTransformation, + interactionSource = interactionSource, + cursorBrush = cursorBrush, + lineLimits = lineLimits, + keyboardOptions = keyboardOptions, + decorator = { innerTextField -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Box(modifier = Modifier.weight(1f)) { + if (textFieldState.text.isEmpty()) { + Text( + text = placeholder, + style = NekiTheme.typography.body16Regular, + color = NekiTheme.colorScheme.gray300, + ) + } + innerTextField() + } + maxLength?.let { + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "${textFieldState.text.length}/$maxLength", + style = NekiTheme.typography.caption12Regular, + color = NekiTheme.colorScheme.gray300, + ) + } + } + }, + ) + } +} + +@Composable +fun NekiTextFieldWithError( + textFieldState: TextFieldState, + modifier: Modifier = Modifier, + titleLabel: String? = null, + placeholder: String = "", + maxLength: Int? = null, + isError: Boolean = false, + errorMessage: String? = null, + textStyle: TextStyle = NekiTheme.typography.body16Medium.copy( + color = NekiTheme.colorScheme.gray900, + ), + cursorBrush: Brush = SolidColor(NekiTheme.colorScheme.gray800), + lineLimits: TextFieldLineLimits = TextFieldLineLimits.SingleLine, + inputTransformation: InputTransformation? = null, + outputTransformation: OutputTransformation? = null, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + NekiTextField( + textFieldState = textFieldState, + titleLabel = titleLabel, + placeholder = placeholder, + maxLength = maxLength, + isError = isError, + textStyle = textStyle, + cursorBrush = cursorBrush, + lineLimits = lineLimits, + inputTransformation = inputTransformation, + outputTransformation = outputTransformation, + keyboardOptions = keyboardOptions, + ) + + Text( + modifier = Modifier.heightIn(min = 16.dp), + text = if (isError) errorMessage.orEmpty() else "", + style = NekiTheme.typography.caption12Regular, + color = NekiTheme.colorScheme.primary600, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun NekiTextFieldPreview() { + NekiTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + NekiTextField( + textFieldState = remember { TextFieldState() }, + placeholder = "플레이스홀더", + ) + + NekiTextField( + textFieldState = remember { TextFieldState("입력된 텍스트") }, + maxLength = 20, + ) + + NekiTextField( + textFieldState = remember { TextFieldState("에러 상태") }, + isError = true, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun NekiTextFieldWithTitleLabelPreview() { + NekiTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + NekiTextField( + textFieldState = remember { TextFieldState() }, + titleLabel = "닉네임", + placeholder = "닉네임을 입력해주세요", + ) + + NekiTextField( + textFieldState = remember { TextFieldState("입력된 텍스트") }, + titleLabel = "닉네임", + maxLength = 10, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun NekiTextFieldWithErrorPreview() { + NekiTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + NekiTextFieldWithError( + textFieldState = remember { TextFieldState() }, + placeholder = "플레이스홀더", + ) + + NekiTextFieldWithError( + textFieldState = remember { TextFieldState("입력된 텍스트") }, + placeholder = "플레이스홀더", + ) + + NekiTextFieldWithError( + textFieldState = remember { TextFieldState("에러 상태") }, + isError = true, + errorMessage = "올바른 값을 입력해주세요", + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun NekiTextFieldWithErrorAndTitleLabelPreview() { + NekiTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + NekiTextFieldWithError( + textFieldState = remember { TextFieldState() }, + titleLabel = "닉네임", + placeholder = "닉네임을 입력해주세요", + maxLength = 10, + ) + + NekiTextFieldWithError( + textFieldState = remember { TextFieldState("입력된 텍스트") }, + placeholder = "플레이스홀더", + maxLength = 10, + ) + + NekiTextFieldWithError( + textFieldState = remember { TextFieldState("에러 상태") }, + titleLabel = "닉네임", + isError = true, + errorMessage = "이미 사용 중인 닉네임입니다", + maxLength = 10, + ) + } + } +} diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/bottomsheet/NekiTextFieldBottomSheet.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/bottomsheet/NekiTextFieldBottomSheet.kt index a57a89977..9043fb1cf 100644 --- a/core/designsystem/src/main/java/com/neki/android/core/designsystem/bottomsheet/NekiTextFieldBottomSheet.kt +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/bottomsheet/NekiTextFieldBottomSheet.kt @@ -1,26 +1,15 @@ package com.neki.android.core.designsystem.bottomsheet -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.input.InputTransformation -import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldState -import androidx.compose.foundation.text.input.maxLength import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet @@ -28,14 +17,10 @@ import androidx.compose.material3.SheetState import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.min import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.NekiTextFieldWithError import com.neki.android.core.designsystem.button.CTAButtonGray import com.neki.android.core.designsystem.button.CTAButtonPrimary import com.neki.android.core.designsystem.ui.theme.NekiTheme @@ -124,20 +109,13 @@ private fun NekiTextFieldBottomSheetContent( Column( verticalArrangement = Arrangement.spacedBy(6.dp), ) { - NekiBottomSheetTextField( + NekiTextFieldWithError( textFieldState = textFieldState, placeholder = placeholder, maxLength = maxLength, isError = isError, keyboardOptions = keyboardOptions, - ) - - // Error message - Text( - modifier = Modifier.heightIn(min = 16.dp), - text = if (isError) errorMessage.orEmpty() else "", - style = NekiTheme.typography.caption12Regular, - color = NekiTheme.colorScheme.primary600, + errorMessage = errorMessage, ) } @@ -163,75 +141,6 @@ private fun NekiTextFieldBottomSheetContent( } } -@Composable -private fun NekiBottomSheetTextField( - textFieldState: TextFieldState, - modifier: Modifier = Modifier, - placeholder: String = "", - maxLength: Int? = null, - isError: Boolean = false, - keyboardOptions: KeyboardOptions = KeyboardOptions.Default, -) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - - val borderColor = when { - isError -> NekiTheme.colorScheme.primary600 - isFocused -> NekiTheme.colorScheme.gray700 - else -> NekiTheme.colorScheme.gray75 - } - - BasicTextField( - state = textFieldState, - modifier = modifier - .fillMaxWidth() - .background( - color = NekiTheme.colorScheme.white, - shape = RoundedCornerShape(8.dp), - ) - .border( - width = 1.dp, - color = borderColor, - shape = RoundedCornerShape(8.dp), - ) - .padding(horizontal = 16.dp, vertical = 13.dp), - textStyle = NekiTheme.typography.body16Medium.copy( - color = NekiTheme.colorScheme.gray900, - ), - inputTransformation = maxLength?.let { InputTransformation.maxLength(it) }, - interactionSource = interactionSource, - cursorBrush = SolidColor(NekiTheme.colorScheme.gray800), - lineLimits = TextFieldLineLimits.SingleLine, - keyboardOptions = keyboardOptions, - decorator = { innerTextField -> - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Box(modifier = Modifier.weight(1f)) { - if (textFieldState.text.isEmpty()) { - Text( - text = placeholder, - style = NekiTheme.typography.body16Regular, - color = NekiTheme.colorScheme.gray300, - ) - } - innerTextField() - } - maxLength?.let { - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = "${textFieldState.text.length}/$maxLength", - style = NekiTheme.typography.caption12Regular, - color = NekiTheme.colorScheme.gray300, - ) - } - } - }, - ) -} - @ComponentPreview @Composable private fun NekiTextFieldBottomSheetContentDefaultPreview() { diff --git a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/const/MyPageConst.kt b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/const/MyPageConst.kt new file mode 100644 index 000000000..6af7f4363 --- /dev/null +++ b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/const/MyPageConst.kt @@ -0,0 +1,5 @@ +package com.neki.android.feature.mypage.impl.const + +internal object MyPageConst { + internal const val NICKNAME_MAX_LENGTH = 10 +} diff --git a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt index 620b70ef5..c7995b4b6 100644 --- a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt +++ b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt @@ -1,21 +1,12 @@ package com.neki.android.feature.mypage.impl.profile -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.input.InputTransformation -import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.maxLength import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.material3.Text import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts @@ -28,11 +19,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.NekiTextField import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.core.ui.component.LoadingDialog import com.neki.android.core.ui.compose.collectWithLifecycle @@ -48,6 +39,7 @@ import com.neki.android.feature.mypage.impl.profile.component.EditProfileImage import com.neki.android.feature.mypage.impl.profile.component.ProfileEditTopBar import com.neki.android.feature.mypage.impl.profile.component.SelectProfileImageDialog import com.neki.android.feature.mypage.impl.profile.component.ProfileImageOption +import com.neki.android.feature.mypage.impl.const.MyPageConst import timber.log.Timber @Composable @@ -120,60 +112,16 @@ fun EditProfileScreen( profileImage = displayProfileImage, onClickCameraIcon = { onIntent(MyPageIntent.ClickCameraIcon) }, ) - Column( + NekiTextField( + textFieldState = textFieldState, modifier = Modifier .fillMaxWidth() .padding(horizontal = 20.dp), - verticalArrangement = Arrangement.spacedBy(6.dp), - ) { - Text( - text = "닉네임", - style = NekiTheme.typography.body14Medium, - color = NekiTheme.colorScheme.gray700, - ) - BasicTextField( - state = textFieldState, - modifier = Modifier - .fillMaxWidth() - .background( - color = NekiTheme.colorScheme.white, - shape = RoundedCornerShape(8.dp), - ) - .border( - width = 1.dp, - color = if (textFieldState.text.isEmpty()) NekiTheme.colorScheme.gray75 else NekiTheme.colorScheme.gray700, - shape = RoundedCornerShape(8.dp), - ) - .padding(horizontal = 16.dp, vertical = 13.dp), - textStyle = NekiTheme.typography.body16Medium.copy( - color = NekiTheme.colorScheme.gray900, - ), - inputTransformation = InputTransformation.maxLength(12), - cursorBrush = SolidColor(NekiTheme.colorScheme.gray800), - lineLimits = TextFieldLineLimits.SingleLine, - decorator = { innerTextField -> - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Box(modifier = Modifier.weight(1f)) { - if (textFieldState.text.isEmpty()) { - Text( - text = "닉네임을 입력해주세요.", - style = NekiTheme.typography.body16Regular, - color = NekiTheme.colorScheme.gray300, - ) - } - innerTextField() - } - Text( - text = "${textFieldState.text.length}/12", - style = NekiTheme.typography.caption12Regular, - color = NekiTheme.colorScheme.gray300, - ) - } - }, - ) - } + titleLabel = "닉네임", + placeholder = "닉네임을 입력해주세요.", + maxLength = MyPageConst.NICKNAME_MAX_LENGTH, + inputTransformation = InputTransformation.maxLength(MyPageConst.NICKNAME_MAX_LENGTH), + ) } if (uiState.isShowImageSelectDialog) { From 81b8da27f43e0e2aef71bae08e0db98a91600285 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:03:49 +0900 Subject: [PATCH 05/30] =?UTF-8?q?[design]=20#108:=20ToolTipPopup=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ToolTipColor enum 추가 (Gray800/Gray25 배경·텍스트·아이콘 색상 매핑) - ArrowDirection(Up/Down), arrowAlignment, arrowPosition 파라미터 도입 - TriangleArrow 컴포넌트 분리 - hasCloseButton 지원 - 프리뷰 24개 추가 (방향·정렬·컬러·닫기버튼 조합) --- .../core/designsystem/popup/ToolTipPopup.kt | 353 +++++++++++++++--- .../impl/main/component/ArchiveMainTopBar.kt | 4 +- 2 files changed, 309 insertions(+), 48 deletions(-) diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt index 1ea95a9bd..4e59fa71a 100644 --- a/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt @@ -2,33 +2,55 @@ package com.neki.android.core.designsystem.popup import androidx.compose.foundation.Canvas import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.R import com.neki.android.core.designsystem.ui.theme.NekiTheme +private const val DEFAULT_ARROW_POSITION = 16 + +enum class ArrowDirection { + Up, Down, +} + +enum class ToolTipColor { + Gray800, Gray25 +} + @Composable fun ToolTipPopup( tooltipText: String, - color: Color, offset: IntOffset, + arrowDirection: ArrowDirection, alignment: Alignment, - onDismissRequest: () -> Unit, + arrowAlignment: Alignment, + arrowPosition: Dp = DEFAULT_ARROW_POSITION.dp, + toolTipColor: ToolTipColor = ToolTipColor.Gray800, + hasCloseButton: Boolean = false, + onDismissRequest: () -> Unit = {}, ) { Popup( alignment = alignment, @@ -37,80 +59,317 @@ fun ToolTipPopup( ) { ToolTipContent( tooltipText = tooltipText, - color = color, + toolTipColor = toolTipColor, + arrowDirection = arrowDirection, + arrowPosition = arrowPosition, + arrowAlignment = arrowAlignment, + hasCloseButton = hasCloseButton, ) } } +@Composable +private fun TriangleArrow( + direction: ArrowDirection, + color: Color, + modifier: Modifier = Modifier, + width: Dp = 10.dp, + height: Dp = 8.dp, +) { + Canvas( + modifier = modifier.size(width = width, height = height), + ) { + val cornerRadius = 1.dp.toPx() + val path = when (direction) { + ArrowDirection.Up -> Path().apply { + moveTo(0f, size.height) + lineTo(size.width / 2 - cornerRadius, cornerRadius) + quadraticTo(size.width / 2, 0f, size.width / 2 + cornerRadius, cornerRadius) + lineTo(size.width, size.height) + close() + } + + ArrowDirection.Down -> Path().apply { + moveTo(0f, 0f) + lineTo(size.width / 2 - cornerRadius, size.height - cornerRadius) + quadraticTo(size.width / 2, size.height, size.width / 2 + cornerRadius, size.height - cornerRadius) + lineTo(size.width, 0f) + close() + } + } + drawPath(path, color) + } +} + @Composable private fun ToolTipContent( tooltipText: String, - color: Color, + arrowDirection: ArrowDirection, + arrowAlignment: Alignment, modifier: Modifier = Modifier, + toolTipColor: ToolTipColor = ToolTipColor.Gray800, + arrowPosition: Dp = DEFAULT_ARROW_POSITION.dp, + hasCloseButton: Boolean = false, ) { + val backgroundColor = when (toolTipColor) { + ToolTipColor.Gray800 -> NekiTheme.colorScheme.gray800 + ToolTipColor.Gray25 -> NekiTheme.colorScheme.gray25 + } + val textColor = when (toolTipColor) { + ToolTipColor.Gray800 -> NekiTheme.colorScheme.white + ToolTipColor.Gray25 -> NekiTheme.colorScheme.gray900 + } + val iconTint = when (toolTipColor) { + ToolTipColor.Gray800 -> NekiTheme.colorScheme.gray25 + ToolTipColor.Gray25 -> NekiTheme.colorScheme.gray500 + } + Column( modifier = modifier.width(IntrinsicSize.Max), ) { - // 꼬리 (오른쪽 정렬, 오른쪽에서 16dp) - Box( - modifier = Modifier - .fillMaxWidth() - .padding(end = 16.dp), - contentAlignment = Alignment.CenterEnd, - ) { - Canvas( - modifier = Modifier.size(width = 10.dp, height = 8.dp), - ) { - val cornerRadius = 1.dp.toPx() - val path = Path().apply { - // 왼쪽 하단에서 시작 - moveTo(0f, size.height) - // 왼쪽 하단 -> 꼭대기 (둥근 모서리) - lineTo( - size.width / 2 - cornerRadius, - cornerRadius, - ) - quadraticTo( - size.width / 2, - 0f, - size.width / 2 + cornerRadius, - cornerRadius, - ) - // 꼭대기 -> 오른쪽 하단 - lineTo(size.width, size.height) - close() - } - drawPath(path, color) - } + if (arrowDirection == ArrowDirection.Up) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = arrowPosition), + contentAlignment = arrowAlignment, + ) { TriangleArrow(direction = arrowDirection, color = backgroundColor) } } - - // 몸통 - Box( + Row( modifier = Modifier - .background( - color = color, - shape = RoundedCornerShape(8.dp), - ) + .background(color = backgroundColor, shape = RoundedCornerShape(8.dp)) .padding(horizontal = 12.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(2.dp), + verticalAlignment = Alignment.CenterVertically, ) { Text( text = tooltipText, style = NekiTheme.typography.body14Medium, - color = NekiTheme.colorScheme.white, + color = textColor, ) + if (hasCloseButton) { + Icon( + modifier = Modifier.size(16.dp), + imageVector = ImageVector.vectorResource(R.drawable.icon_close), + tint = iconTint, + contentDescription = null, + ) + } + } + if (arrowDirection == ArrowDirection.Down) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = arrowPosition), + contentAlignment = arrowAlignment, + ) { TriangleArrow(direction = arrowDirection, color = backgroundColor) } } } } @ComponentPreview @Composable -private fun ToolTipPopupPreview() { +private fun ToolTipArrowUpPreview() { + NekiTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterStart, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.Center, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterEnd, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterStart, + hasCloseButton = true, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.Center, + hasCloseButton = true, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterEnd, + hasCloseButton = true, + ) + } + } +} + +@ComponentPreview +@Composable +private fun ToolTipArrowDownPreview() { + NekiTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.CenterStart, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.Center, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.CenterEnd, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.CenterStart, + hasCloseButton = true, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.Center, + hasCloseButton = true, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.CenterEnd, + hasCloseButton = true, + ) + } + } +} + +@Preview +@Composable +private fun ToolTipArrowUpGrayPreview() { + NekiTheme { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterStart, + toolTipColor = ToolTipColor.Gray25, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.Center, + toolTipColor = ToolTipColor.Gray25, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterEnd, + toolTipColor = ToolTipColor.Gray25, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterStart, + toolTipColor = ToolTipColor.Gray25, + hasCloseButton = true, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.Center, + toolTipColor = ToolTipColor.Gray25, + hasCloseButton = true, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterEnd, + toolTipColor = ToolTipColor.Gray25, + hasCloseButton = true, + ) + } + } +} + +@Preview +@Composable +private fun ToolTipArrowDownGrayPreview() { NekiTheme { - Box(modifier = Modifier.padding(16.dp)) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.CenterStart, + toolTipColor = ToolTipColor.Gray25, + ) ToolTipContent( - tooltipText = "툴팁 메시지입니다", - color = NekiTheme.colorScheme.gray800, + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.Center, + toolTipColor = ToolTipColor.Gray25, ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.CenterEnd, + toolTipColor = ToolTipColor.Gray25, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.CenterStart, + toolTipColor = ToolTipColor.Gray25, + hasCloseButton = true, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.Center, + toolTipColor = ToolTipColor.Gray25, + hasCloseButton = true, + ) + ToolTipContent( + tooltipText = "텍스트", + arrowDirection = ArrowDirection.Down, + arrowAlignment = Alignment.CenterEnd, + toolTipColor = ToolTipColor.Gray25, + hasCloseButton = true, + ) + } + } +} + +@ComponentPreview +@Composable +private fun TriangleArrowPreview() { + NekiTheme { + Row( + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + TriangleArrow(direction = ArrowDirection.Up, color = NekiTheme.colorScheme.gray800) + TriangleArrow(direction = ArrowDirection.Down, color = NekiTheme.colorScheme.gray800) } } } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTopBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTopBar.kt index 443d40050..f87644bd2 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTopBar.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTopBar.kt @@ -29,6 +29,7 @@ import com.neki.android.core.designsystem.R import com.neki.android.core.designsystem.button.NekiIconButton import com.neki.android.core.designsystem.logo.PrimaryNekiTypoLogo import com.neki.android.core.designsystem.modifier.dropdownShadow +import com.neki.android.core.designsystem.popup.ArrowDirection import com.neki.android.core.designsystem.popup.ToolTipPopup import com.neki.android.core.designsystem.topbar.NekiLeftTitleTopBar import com.neki.android.core.designsystem.ui.theme.NekiTheme @@ -107,9 +108,10 @@ private fun ArchiveToolTip( ToolTipPopup( tooltipText = "버튼을 눌러 네컷을 추가할 수 있어요", - color = NekiTheme.colorScheme.gray800, offset = offset, alignment = Alignment.TopEnd, + arrowDirection = ArrowDirection.Up, + arrowAlignment = Alignment.CenterEnd, onDismissRequest = onDismissRequest, ) } From aec40c8ff251565b6067c8ba0b7c90e330e8f984 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:04:08 +0900 Subject: [PATCH 06/30] =?UTF-8?q?[design]=20#108:=20ItemOverlay=20?= =?UTF-8?q?=EB=A6=AC=EB=84=A4=EC=9E=84=20=EB=B0=8F=20=ED=94=84=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PhotoGridItemOverlay → GridItemOverlay 리네임 - SelectedPhotoGridItemOverlay 프리뷰 추가 - 사용처 반영 (SelectablePhotoItem, ArchiveMainPhotoItem) --- .../android/core/ui/component/ItemOverlay.kt | 32 +++++++++++++++---- .../impl/component/SelectablePhotoItem.kt | 14 ++------ .../main/component/ArchiveMainPhotoItem.kt | 4 +-- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt index 661f1104e..5d0d70ddb 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt @@ -1,6 +1,7 @@ package com.neki.android.core.ui.component import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape @@ -14,7 +15,7 @@ import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.ui.theme.NekiTheme @Composable -fun PhotoGridItemOverlay( +fun GridItemOverlay( modifier: Modifier = Modifier, shape: Shape = RoundedCornerShape(8.dp), ) { @@ -42,18 +43,35 @@ fun SelectedPhotoGridItemOverlay( shape: Shape = RoundedCornerShape(8.dp), ) { Box( - modifier = modifier.background( - color = Color.Black.copy(alpha = 0.2f), - shape = shape, - ), + modifier = modifier + .border( + width = 2.dp, + color = NekiTheme.colorScheme.primary400, + shape = RoundedCornerShape(8.dp), + ) + .background( + color = Color.Black.copy(alpha = 0.2f), + shape = shape, + ), ) } @ComponentPreview @Composable -private fun PhotoGridItemOverlayPreview() { +private fun GridItemOverlayPreview() { + NekiTheme { + GridItemOverlay( + modifier = Modifier + .size(80.dp), + ) + } +} + +@ComponentPreview +@Composable +private fun SelectedPhotoGridItemOverlayPreview() { NekiTheme { - PhotoGridItemOverlay( + SelectedPhotoGridItemOverlay( modifier = Modifier .size(80.dp), ) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelectablePhotoItem.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelectablePhotoItem.kt index 41e761506..cd6b4e0cb 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelectablePhotoItem.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelectablePhotoItem.kt @@ -1,6 +1,5 @@ package com.neki.android.feature.archive.impl.component -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -19,7 +18,7 @@ import com.neki.android.core.designsystem.modifier.noRippleClickable import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.core.model.Photo import com.neki.android.core.ui.component.PhotoComponent -import com.neki.android.core.ui.component.PhotoGridItemOverlay +import com.neki.android.core.ui.component.GridItemOverlay import com.neki.android.core.ui.component.SelectedPhotoGridItemOverlay import com.neki.android.core.ui.component.SelectionCheckbox @@ -37,14 +36,7 @@ internal fun SelectablePhotoItem( ) { PhotoComponent( photo = photo, - modifier = Modifier.then( - if (isSelected) - Modifier.border( - width = 2.dp, - color = NekiTheme.colorScheme.primary400, - shape = RoundedCornerShape(8.dp), - ) else Modifier.clip(RoundedCornerShape(8.dp)), - ), + modifier = Modifier.clip(RoundedCornerShape(8.dp)), onClickItem = onClickItem, ) @@ -54,7 +46,7 @@ internal fun SelectablePhotoItem( shape = RoundedCornerShape(8.dp), ) } else { - PhotoGridItemOverlay( + GridItemOverlay( modifier = Modifier.matchParentSize(), shape = RoundedCornerShape(8.dp), ) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainPhotoItem.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainPhotoItem.kt index 14b36cc0d..a341d713e 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainPhotoItem.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainPhotoItem.kt @@ -16,7 +16,7 @@ import com.neki.android.core.designsystem.R import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.core.model.Photo import com.neki.android.core.ui.component.PhotoComponent -import com.neki.android.core.ui.component.PhotoGridItemOverlay +import com.neki.android.core.ui.component.GridItemOverlay @Composable internal fun ArchiveMainPhotoItem( @@ -32,7 +32,7 @@ internal fun ArchiveMainPhotoItem( onClickItem = onClickItem, ) - PhotoGridItemOverlay( + GridItemOverlay( modifier = Modifier.matchParentSize(), shape = RoundedCornerShape(8.dp), ) From 1cc5636e0ea08ead7963cd29d3cf32200bba0883 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:04:27 +0900 Subject: [PATCH 07/30] =?UTF-8?q?[design]=20#108:=20TopBar=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TopBarTextButton 삭제 - NekiTopBar 수정 --- .../designsystem/button/TopBarTextButton.kt | 58 ------------------- .../core/designsystem/topbar/NekiTopBar.kt | 32 +++++----- 2 files changed, 16 insertions(+), 74 deletions(-) delete mode 100644 core/designsystem/src/main/java/com/neki/android/core/designsystem/button/TopBarTextButton.kt diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/button/TopBarTextButton.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/button/TopBarTextButton.kt deleted file mode 100644 index 252b204ca..000000000 --- a/core/designsystem/src/main/java/com/neki/android/core/designsystem/button/TopBarTextButton.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.neki.android.core.designsystem.button - -import androidx.compose.foundation.layout.offset -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.LayoutDirection -import com.neki.android.core.designsystem.ComponentPreview -import com.neki.android.core.designsystem.ui.theme.NekiTheme - -@Composable -fun TopBarTextButton( - buttonText: String, - modifier: Modifier = Modifier, - enabled: Boolean = true, - enabledTextColor: Color = NekiTheme.colorScheme.primary500, - disabledTextColor: Color = NekiTheme.colorScheme.gray200, - onClick: () -> Unit = {}, -) { - NekiTextButton( - modifier = modifier.offset( - x = ButtonDefaults.TextButtonContentPadding.calculateLeftPadding(LayoutDirection.Ltr), - ), - onClick = onClick, - enabled = enabled, - ) { - Text( - text = buttonText, - style = NekiTheme.typography.body16SemiBold, - color = if (enabled) enabledTextColor else disabledTextColor, - ) - } -} - -@ComponentPreview -@Composable -private fun EnabledTopBarTextButtonPreview() { - NekiTheme { - TopBarTextButton( - buttonText = "텍스트버튼", - onClick = {}, - ) - } -} - -@ComponentPreview -@Composable -private fun DisabledTopBarTextButtonPreview() { - NekiTheme { - TopBarTextButton( - buttonText = "텍스트버튼", - onClick = {}, - enabled = false, - ) - } -} diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/topbar/NekiTopBar.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/topbar/NekiTopBar.kt index d27b24b7a..e335eb3aa 100644 --- a/core/designsystem/src/main/java/com/neki/android/core/designsystem/topbar/NekiTopBar.kt +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/topbar/NekiTopBar.kt @@ -35,37 +35,37 @@ fun NekiTitleTopBar( } @Composable -fun NekiLeftTitleTopBar( +private fun NekiTopBar( modifier: Modifier = Modifier, - title: @Composable (() -> Unit)? = null, - actions: @Composable (() -> Unit)? = null, + title: @Composable ((Modifier) -> Unit)? = null, + leadingIcon: @Composable ((Modifier) -> Unit)? = null, + actions: @Composable ((Modifier) -> Unit)? = null, ) { - Row( + Box( modifier = modifier .fillMaxWidth() .height(54.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, ) { - title?.invoke() - actions?.invoke() + leadingIcon?.invoke(Modifier.align(Alignment.CenterStart)) + title?.invoke(Modifier.align(Alignment.Center)) + actions?.invoke(Modifier.align(Alignment.CenterEnd)) } } @Composable -private fun NekiTopBar( +fun NekiLeftTitleTopBar( modifier: Modifier = Modifier, - title: @Composable ((Modifier) -> Unit)? = null, - leadingIcon: @Composable ((Modifier) -> Unit)? = null, - actions: @Composable ((Modifier) -> Unit)? = null, + title: @Composable (() -> Unit)? = null, + actions: @Composable (() -> Unit)? = null, ) { - Box( + Row( modifier = modifier .fillMaxWidth() .height(54.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, ) { - leadingIcon?.invoke(Modifier.align(Alignment.CenterStart)) - title?.invoke(Modifier.align(Alignment.Center)) - actions?.invoke(Modifier.align(Alignment.CenterEnd)) + title?.invoke() + actions?.invoke() } } From 4d12868fc94ba57e6b15a5b4ba1a06c2675cf750 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:12:27 +0900 Subject: [PATCH 08/30] =?UTF-8?q?[design]=20#108:=20NekiTextButton=20?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=83=89=EC=83=81=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - enabledTextColor, disabledTextColor 파라미터 추가 - MaterialTheme → NekiTheme 색상으로 변경 --- .../core/designsystem/button/NekiTextButton.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/button/NekiTextButton.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/button/NekiTextButton.kt index cc9062c67..aaccab371 100644 --- a/core/designsystem/src/main/java/com/neki/android/core/designsystem/button/NekiTextButton.kt +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/button/NekiTextButton.kt @@ -2,13 +2,12 @@ package com.neki.android.core.designsystem.button import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.graphics.Color import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.modifier.MultipleEventsCutter import com.neki.android.core.designsystem.modifier.get @@ -20,6 +19,8 @@ fun NekiTextButton( modifier: Modifier = Modifier, enabled: Boolean = true, multipleEventsCutterEnabled: Boolean = true, + enabledTextColor: Color = NekiTheme.colorScheme.primary500, + disabledTextColor: Color = NekiTheme.colorScheme.gray200, contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding, content: @Composable () -> Unit = {}, ) { @@ -36,8 +37,8 @@ fun NekiTextButton( contentPadding = contentPadding, enabled = enabled, colors = ButtonDefaults.textButtonColors( - contentColor = MaterialTheme.colorScheme.onSurfaceVariant, - disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant, + contentColor = enabledTextColor, + disabledContentColor = disabledTextColor, ), ) { content() @@ -52,8 +53,7 @@ private fun NekiTextButtonPreview() { onClick = {}, ) { Text( - text = "Text Button", - textDecoration = TextDecoration.Underline, + text = "텍스트버튼", ) } } From 12da32f3e96b1b3d1bdf9aeb47521107ca72bcfd Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:12:37 +0900 Subject: [PATCH 09/30] =?UTF-8?q?[design]=20#108:=20Background=20Modifier?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20GridItemOverlay=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - photoBackground(), poseBackground() Modifier 삭제 - PoseListContent에서 poseBackground() → GridItemOverlay로 교체 --- .../core/designsystem/modifier/Background.kt | 40 ------------------- .../impl/main/component/PoseListContent.kt | 12 +++--- 2 files changed, 5 insertions(+), 47 deletions(-) diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Background.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Background.kt index df3370121..626dbe6d8 100644 --- a/core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Background.kt +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Background.kt @@ -1,54 +1,14 @@ package com.neki.android.core.designsystem.modifier import androidx.compose.foundation.background -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeStyle import dev.chrisbanes.haze.HazeTint import dev.chrisbanes.haze.hazeEffect -/** - * 사진 컴포넌트에 적용되는 그라데이션 배경 - * 좌하단에서 우상단으로 갈수록 어두워지는 효과 - */ -fun Modifier.photoBackground( - shape: Shape = RoundedCornerShape(12.dp), -): Modifier = this.background( - brush = Brush.linearGradient( - colorStops = arrayOf( - 0f to Color.Black.copy(alpha = 0f), - 0.7f to Color.Black.copy(alpha = 0.09f), - 1f to Color.Black.copy(alpha = 0.3f), - ), - start = Offset(0f, Float.POSITIVE_INFINITY), - end = Offset(Float.POSITIVE_INFINITY, 0f), - ), - shape = shape, -) - -/** - * 포즈 컴포넌트에 적용되는 그라데이션 배경 - * 상단에서 134/242 지점까지 어두워지는 효과 - */ -fun Modifier.poseBackground( - shape: Shape = RoundedCornerShape(12.dp), -): Modifier = this.background( - brush = Brush.verticalGradient( - colorStops = arrayOf( - 0f to Color.Black.copy(alpha = 0.2f), - 134f / 242f to Color.Black.copy(alpha = 0f), - ), - ), - shape = shape, -) - /** * 블러 효과가 적용된 배경을 설정하는 Modifier 확장 함수 * diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/PoseListContent.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/PoseListContent.kt index 19e11d78a..17cd2b964 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/PoseListContent.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/PoseListContent.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.vectorResource @@ -26,11 +25,11 @@ import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.itemKey import coil3.compose.AsyncImage import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.R import com.neki.android.core.designsystem.modifier.noRippleClickable -import com.neki.android.core.designsystem.modifier.poseBackground import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.core.model.Pose -import com.neki.android.core.designsystem.R +import com.neki.android.core.ui.component.GridItemOverlay import com.neki.android.feature.pose.impl.const.PoseConst.POSE_LAYOUT_BOTTOM_PADDING import com.neki.android.feature.pose.impl.const.PoseConst.POSE_LAYOUT_VERTICAL_SPACING @@ -83,10 +82,9 @@ private fun PoseItem( contentDescription = null, contentScale = ContentScale.FillWidth, ) - Box( - modifier = Modifier - .matchParentSize() - .poseBackground(shape = RectangleShape), + GridItemOverlay( + modifier = Modifier.matchParentSize(), + shape = RoundedCornerShape(12.dp), ) if (pose.isScrapped) { Icon( From 7c1d8e7f07d46b80a26d3713679c6f8f49247d09 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:12:50 +0900 Subject: [PATCH 10/30] =?UTF-8?q?[design]=20#108:=20DoubleButtonOptionBott?= =?UTF-8?q?omSheet=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 타이틀, 옵션, 버튼 영역 패딩 및 간격 재조정 --- .../component/DoubleButtonOptionBottomSheet.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/DoubleButtonOptionBottomSheet.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/DoubleButtonOptionBottomSheet.kt index 5be1d7652..a34b200b1 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/DoubleButtonOptionBottomSheet.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/DoubleButtonOptionBottomSheet.kt @@ -26,6 +26,7 @@ import com.neki.android.core.designsystem.button.CTAButtonGray import com.neki.android.core.designsystem.button.CTAButtonPrimary import com.neki.android.core.designsystem.modifier.noRippleClickable import com.neki.android.core.designsystem.ui.theme.NekiTheme +import com.neki.android.core.ui.compose.VerticalSpacer import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -83,18 +84,17 @@ internal fun DoubleButtonOptionBottomSheetContent( Column( modifier = modifier .fillMaxWidth() - .padding(horizontal = 20.dp) - .padding(bottom = 34.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), + .padding(top = 4.dp, bottom = 34.dp), ) { Text( + modifier = Modifier.padding(horizontal = 20.dp), text = title, style = NekiTheme.typography.title20SemiBold, color = NekiTheme.colorScheme.gray900, ) - + VerticalSpacer(12.dp) Column( - verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.fillMaxWidth(), ) { options.forEach { option -> OptionRow( @@ -104,9 +104,12 @@ internal fun DoubleButtonOptionBottomSheetContent( ) } } + VerticalSpacer(16.dp) Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { CTAButtonGray( @@ -136,7 +139,7 @@ private fun OptionRow( modifier = modifier .fillMaxWidth() .noRippleClickable(onClick = onClick) - .padding(vertical = 12.dp), + .padding(horizontal = 20.dp, vertical = 14.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { From bde7d0d33af41ee254d9f2c064c3fe5e69081a6d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:13:01 +0900 Subject: [PATCH 11/30] =?UTF-8?q?[design]=20#108:=20QR=20=EC=8A=A4?= =?UTF-8?q?=EC=BA=90=EB=84=88=20=ED=86=A0=EC=B9=98=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EC=A6=88=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토치 버튼 48dp → 56dp, 아이콘 28dp 명시 --- .../photo_upload/impl/qrscan/component/QRScannerContent.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/QRScannerContent.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/QRScannerContent.kt index 40c3b1513..ba83e8d8d 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/QRScannerContent.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/component/QRScannerContent.kt @@ -161,7 +161,7 @@ internal fun QRScannerContent( NekiIconButton( modifier = Modifier .padding(top = 37.dp) - .size(48.dp) + .size(56.dp) .clip(CircleShape) .background( if (isTorchEnabled) Color.White @@ -170,6 +170,7 @@ internal fun QRScannerContent( onClick = { onIntent(QRScanIntent.ToggleTorch) }, ) { Icon( + modifier = Modifier.size(28.dp), imageVector = ImageVector.vectorResource( if (isTorchEnabled) R.drawable.icon_torch_on else R.drawable.icon_torch_off, From f7dfb299a28c6f736fdf062ada66da94f284e5c3 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:13:11 +0900 Subject: [PATCH 12/30] =?UTF-8?q?[design]=20#108:=20=ED=8F=AC=EC=A6=88=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EC=B9=A9=20=EA=B7=B8=EB=A6=BC=EC=9E=90=20?= =?UTF-8?q?=EB=B0=8F=20=EC=82=AC=EC=9D=B4=EC=A6=88=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - buttonShadow() 추가, 아이콘 20dp → 24dp --- .../feature/pose/impl/main/component/RecommendationChip.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt index 35c2c8cbe..5025c3093 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.R +import com.neki.android.core.designsystem.modifier.buttonShadow import com.neki.android.core.designsystem.modifier.clickableSingle import com.neki.android.core.designsystem.ui.theme.NekiTheme @@ -28,6 +29,7 @@ internal fun RecommendationChip( Row( modifier = modifier .clip(CircleShape) + .buttonShadow() .clickableSingle(onClick = onClick) .background(shape = CircleShape, color = NekiTheme.colorScheme.gray800) .padding(horizontal = 16.dp, vertical = 8.dp), @@ -35,7 +37,7 @@ internal fun RecommendationChip( verticalAlignment = Alignment.CenterVertically, ) { Icon( - modifier = Modifier.size(20.dp), + modifier = Modifier.size(24.dp), imageVector = ImageVector.vectorResource(R.drawable.icon_repeat), contentDescription = null, tint = NekiTheme.colorScheme.primary400, From eebe53f5bd70608f3e080b942778404c4c578bc0 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:28:53 +0900 Subject: [PATCH 13/30] =?UTF-8?q?[design]=20#108:=20=EC=95=84=EC=B9=B4?= =?UTF-8?q?=EC=9D=B4=EB=B9=99=20=EC=97=A0=ED=8B=B0=20=EB=B7=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/image_empty_photo.png | Bin 0 -> 10736 bytes .../impl/album_detail/AlbumDetailScreen.kt | 4 +- .../impl/component/EmptyAlbumContent.kt | 55 ++++++++++++ .../archive/impl/component/EmptyContent.kt | 80 ------------------ .../archive/impl/main/ArchiveMainScreen.kt | 9 +- .../impl/main/component/EmptyContent.kt | 54 ++++++++++++ .../impl/main/component/NoPhotoContent.kt | 47 ---------- .../archive/impl/photo/AllPhotoScreen.kt | 4 +- 8 files changed, 120 insertions(+), 133 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/image_empty_photo.png create mode 100644 feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyAlbumContent.kt delete mode 100644 feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyContent.kt create mode 100644 feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/EmptyContent.kt delete mode 100644 feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/NoPhotoContent.kt diff --git a/core/designsystem/src/main/res/drawable/image_empty_photo.png b/core/designsystem/src/main/res/drawable/image_empty_photo.png new file mode 100644 index 0000000000000000000000000000000000000000..0ac5e57d5a3623dde425aa6196486fe7882efcbf GIT binary patch literal 10736 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 iw52U=X-iukl>9aS8m!N14(oyd0000 Unit = {}, +) { + Box( + modifier = modifier.fillMaxSize(), + ) { + EmptyTopBar( + title = title, + onClickBack = onClickBack, + ) + + EmptyContent( + modifier = Modifier.align(Alignment.Center), + emptyText = "아직 등록된 사진이 없어요\n새로운 사진을 등록하고 앨범에 추가해보세요!", + ) + } +} + +@Composable +private fun EmptyTopBar( + title: String, + onClickBack: () -> Unit, + modifier: Modifier = Modifier, +) { + BackTitleTopBar( + modifier = modifier, + title = title, + onBack = onClickBack, + ) +} + +@ComponentPreview +@Composable +private fun EmptyAlbumContentPreview() { + NekiTheme { + EmptyAlbumContent( + title = "즐겨찾기", + ) + } +} diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyContent.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyContent.kt deleted file mode 100644 index 402073e14..000000000 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyContent.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.neki.android.feature.archive.impl.component - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.neki.android.core.designsystem.ComponentPreview -import com.neki.android.core.designsystem.R -import com.neki.android.core.designsystem.topbar.BackTitleTopBar -import com.neki.android.core.designsystem.ui.theme.NekiTheme - -private const val EMPTY_TEXT = "아직 등록된 사진이 없어요\n새로운 사진을 등록하고 앨범에 추가해보세요!" - -@Composable -internal fun EmptyContent( - title: String, - modifier: Modifier = Modifier, - emptyText: String = EMPTY_TEXT, - onClickBack: () -> Unit = {}, -) { - Box( - modifier = modifier.fillMaxSize(), - ) { - EmptyTopBar( - title = title, - onClickBack = onClickBack, - ) - - Column( - modifier = Modifier.align(Alignment.Center), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.icon_empty_content), - contentDescription = null, - tint = Color.Unspecified, - ) - Text( - text = emptyText, - style = NekiTheme.typography.body14Medium, - color = NekiTheme.colorScheme.gray300, - textAlign = TextAlign.Center, - ) - } - } -} - -@Composable -private fun EmptyTopBar( - title: String, - onClickBack: () -> Unit, - modifier: Modifier = Modifier, -) { - BackTitleTopBar( - modifier = modifier, - title = title, - onBack = onClickBack, - ) -} - -@ComponentPreview -@Composable -private fun EmptyContentPreview() { - NekiTheme { - EmptyContent( - title = "즐겨찾기", - ) - } -} diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.kt index d3a8b095b..5b6785804 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainScreen.kt @@ -48,7 +48,7 @@ import com.neki.android.feature.archive.impl.main.component.ArchiveMainTopBar import com.neki.android.feature.archive.impl.main.component.AlbumUploadOption import com.neki.android.feature.archive.impl.main.component.SelectWithAlbumDialog import com.neki.android.feature.archive.impl.main.component.GotoTopButton -import com.neki.android.feature.archive.impl.main.component.NoPhotoContent +import com.neki.android.feature.archive.impl.main.component.EmptyContent import kotlinx.collections.immutable.persistentListOf import timber.log.Timber @@ -243,7 +243,12 @@ private fun ArchiveMainContent( if (uiState.recentPhotos.isEmpty()) { item(span = StaggeredGridItemSpan.FullLine) { - NoPhotoContent() + EmptyContent( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 70.dp), + emptyText = "아직 등록된 사진이 없어요\n찍은 네컷을 저장해보세요!", + ) } } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/EmptyContent.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/EmptyContent.kt new file mode 100644 index 000000000..100d47921 --- /dev/null +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/EmptyContent.kt @@ -0,0 +1,54 @@ +package com.neki.android.feature.archive.impl.main.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.R +import com.neki.android.core.designsystem.ui.theme.NekiTheme + +@Composable +internal fun EmptyContent( + emptyText: String, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Image( + modifier = Modifier + .width(148.dp) + .height(112.dp), + painter = painterResource(R.drawable.image_empty_photo), + contentDescription = null, + ) + Text( + text = emptyText, + style = NekiTheme.typography.body14Medium, + color = NekiTheme.colorScheme.gray200, + textAlign = TextAlign.Center, + ) + } +} + +@ComponentPreview +@Composable +private fun EmptyContentPreview() { + NekiTheme { + EmptyContent( + emptyText = "아직 등록된 사진이 없어요\n찍은 네컷을 네키에 저장해보세요!", + ) + } +} diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/NoPhotoContent.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/NoPhotoContent.kt deleted file mode 100644 index cfc049feb..000000000 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/NoPhotoContent.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.neki.android.feature.archive.impl.main.component - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.neki.android.core.designsystem.ui.theme.NekiTheme - -@Composable -internal fun NoPhotoContent( - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier - .fillMaxWidth() - .padding(vertical = 70.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(22.dp), - ) { - Box( - modifier = Modifier - .clip(CircleShape) - .size(104.dp) - .background( - color = NekiTheme.colorScheme.gray50, - shape = CircleShape, - ), - ) - Text( - text = "아직 등록된 사진이 없어요\n찍은 네컷을 네키에 저장해보세요!", - style = NekiTheme.typography.body14Medium, - color = NekiTheme.colorScheme.gray300, - textAlign = TextAlign.Center, - ) - } -} diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoScreen.kt index 4282a8bd0..aa8c35d72 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/AllPhotoScreen.kt @@ -41,7 +41,7 @@ import com.neki.android.core.ui.component.LoadingDialog import com.neki.android.core.ui.compose.collectWithLifecycle import com.neki.android.core.ui.toast.NekiToast import com.neki.android.feature.archive.impl.component.DeletePhotoDialog -import com.neki.android.feature.archive.impl.component.EmptyContent +import com.neki.android.feature.archive.impl.component.EmptyAlbumContent import com.neki.android.feature.archive.impl.component.SelectablePhotoItem import com.neki.android.feature.archive.impl.const.ArchiveConst.ARCHIVE_GRID_ITEM_SPACING import com.neki.android.feature.archive.impl.const.ArchiveConst.PHOTO_GRAY_LAYOUT_BOTTOM_PADDING @@ -123,7 +123,7 @@ internal fun AllPhotoScreen( } if (isEmpty) { - EmptyContent( + EmptyAlbumContent( title = "모든 사진", onClickBack = { onIntent(AllPhotoIntent.ClickTopBarBackIcon) }, ) From 1eaa5da2efd16f7f1506f31d31f425fa624ff2a6 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:30:30 +0900 Subject: [PATCH 14/30] =?UTF-8?q?[design]=20#108:=20=EB=B9=88=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/image_empty_folder.png | Bin 0 -> 11975 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/image_empty_folder.png diff --git a/core/designsystem/src/main/res/drawable/image_empty_folder.png b/core/designsystem/src/main/res/drawable/image_empty_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e6dfed63dc9300787a20a67188fcc2773005aa GIT binary patch literal 11975 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 Z{|A4#8f81iqYwZ9002ovPDHLkV1h4UZ?^yd literal 0 HcmV?d00001 From c4362023b9002f9b30e01ca4927a4b7b8402c4b5 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 02:41:45 +0900 Subject: [PATCH 15/30] =?UTF-8?q?[feat]=20#108:=20=EC=95=A8=EB=B2=94?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=A7=84=EC=9D=B4=20=EC=97=86=EC=9D=84=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0,=20=EC=8D=B8=EB=84=A4=EC=9D=BC=EC=97=90=20?= =?UTF-8?q?=EB=B9=88=20=EA=B0=A4=EB=9F=AC=EB=A6=AC=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../drawable/icon_empty_gallery_thumbnail.xml | 26 +++ .../core/ui/component/AlbumRowComponent.kt | 151 +++++++++++++----- .../main/component/ArchiveMainAlbumList.kt | 64 ++++++-- 3 files changed, 182 insertions(+), 59 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/icon_empty_gallery_thumbnail.xml diff --git a/core/designsystem/src/main/res/drawable/icon_empty_gallery_thumbnail.xml b/core/designsystem/src/main/res/drawable/icon_empty_gallery_thumbnail.xml new file mode 100644 index 000000000..7705b019e --- /dev/null +++ b/core/designsystem/src/main/res/drawable/icon_empty_gallery_thumbnail.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt index ac9141969..949f8b186 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.vectorResource @@ -41,6 +42,7 @@ fun FavoriteAlbumRowComponent( horizontalArrangement = Arrangement.spacedBy(16.dp), ) { FavoriteAlbumThumbnail( + isEmpty = album.photoCount == 0, thumbnailUrl = album.thumbnailUrl, ) @@ -68,6 +70,7 @@ fun AlbumRowComponent( horizontalArrangement = Arrangement.spacedBy(16.dp), ) { AlbumThumbnail( + isEmpty = album.photoCount == 0, thumbnailUrl = album.thumbnailUrl, ) @@ -88,6 +91,7 @@ fun AlbumRowComponent( @Composable private fun FavoriteAlbumThumbnail( thumbnailUrl: String?, + isEmpty: Boolean, modifier: Modifier = Modifier, ) { Box( @@ -96,45 +100,80 @@ private fun FavoriteAlbumThumbnail( .clip(RoundedCornerShape(8.dp)), contentAlignment = Alignment.Center, ) { - AsyncImage( - modifier = Modifier.matchParentSize(), - model = thumbnailUrl, - contentDescription = null, - contentScale = ContentScale.Crop, - ) + if (isEmpty || thumbnailUrl == null) { + Box( + modifier = Modifier + .matchParentSize() + .background(NekiTheme.colorScheme.gray50), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.size(28.dp), + imageVector = ImageVector.vectorResource(R.drawable.icon_empty_gallery_thumbnail), + tint = NekiTheme.colorScheme.gray100, + contentDescription = null, + ) + } + } else { + AsyncImage( + modifier = Modifier + .background(color = Color.Black.copy(alpha = 0.04f)) + .matchParentSize(), + model = thumbnailUrl, + contentDescription = null, + contentScale = ContentScale.Crop, + ) - Box( - modifier = Modifier - .matchParentSize() - .background(NekiTheme.colorScheme.favoriteAlbumCover.copy(alpha = 0.5f)), - ) + Box( + modifier = Modifier + .matchParentSize() + .background(NekiTheme.colorScheme.favoriteAlbumCover.copy(alpha = 0.5f)), + ) - Icon( - modifier = Modifier.size(20.dp), - imageVector = ImageVector.vectorResource(R.drawable.icon_heart_filled), - contentDescription = null, - tint = NekiTheme.colorScheme.white, - ) + Icon( + modifier = Modifier.size(20.dp), + imageVector = ImageVector.vectorResource(R.drawable.icon_heart_filled), + contentDescription = null, + tint = NekiTheme.colorScheme.white, + ) + } } } @Composable private fun AlbumThumbnail( thumbnailUrl: String?, + isEmpty: Boolean, modifier: Modifier = Modifier, ) { - AsyncImage( - modifier = modifier - .size(72.dp) - .clip(RoundedCornerShape(8.dp)) - .background( - color = NekiTheme.colorScheme.gray50, - shape = RoundedCornerShape(8.dp), - ), - model = thumbnailUrl, - contentDescription = null, - contentScale = ContentScale.Crop, - ) + if (isEmpty) { + Box( + modifier = Modifier + .size(72.dp) + .background(NekiTheme.colorScheme.gray50), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.size(28.dp), + imageVector = ImageVector.vectorResource(R.drawable.icon_empty_gallery_thumbnail), + tint = NekiTheme.colorScheme.gray100, + contentDescription = null, + ) + } + } else { + AsyncImage( + modifier = modifier + .size(72.dp) + .clip(RoundedCornerShape(8.dp)) + .background( + color = NekiTheme.colorScheme.gray50, + shape = RoundedCornerShape(8.dp), + ), + model = thumbnailUrl, + contentDescription = null, + contentScale = ContentScale.Crop, + ) + } } @Composable @@ -165,12 +204,23 @@ private fun AlbumInfo( @Composable private fun FavoriteAlbumRowComponentPreview() { NekiTheme { - FavoriteAlbumRowComponent( - album = AlbumPreview( - id = 0, - title = "즐겨찾기", - ), - ) + Column { + FavoriteAlbumRowComponent( + album = AlbumPreview( + id = 0, + title = "즐겨찾기", + photoCount = 0, + ), + ) + FavoriteAlbumRowComponent( + album = AlbumPreview( + id = 0, + title = "즐겨찾기", + thumbnailUrl = "https://example.com/photo.jpg", + photoCount = 12, + ), + ) + } } } @@ -178,12 +228,23 @@ private fun FavoriteAlbumRowComponentPreview() { @Composable private fun AlbumRowComponentPreview() { NekiTheme { - AlbumRowComponent( - album = AlbumPreview( - id = 1, - title = "일반앨범제목", - ), - ) + Column { + AlbumRowComponent( + album = AlbumPreview( + id = 1, + title = "빈 앨범", + photoCount = 0, + ), + ) + AlbumRowComponent( + album = AlbumPreview( + id = 2, + title = "일반앨범제목", + thumbnailUrl = "https://example.com/photo.jpg", + photoCount = 5, + ), + ) + } } } @@ -191,20 +252,22 @@ private fun AlbumRowComponentPreview() { @Composable private fun AlbumRowComponentSelectablePreview() { NekiTheme { - Column(verticalArrangement = Arrangement.spacedBy(20.dp)) { + Column { AlbumRowComponent( album = AlbumPreview( id = 1, - title = "선택되지 않은 앨범", + title = "선택되지 않은 빈 앨범", + photoCount = 0, ), isSelectable = true, isSelected = false, ) - AlbumRowComponent( album = AlbumPreview( id = 2, title = "선택된 앨범", + thumbnailUrl = "https://example.com/photo.jpg", + photoCount = 8, ), isSelectable = true, isSelected = true, diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt index 51cd6dc99..bdb302d5b 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt @@ -166,23 +166,40 @@ private fun ArchiveAlbumItem( Box( modifier = modifier + .cardShadow() .height(166.dp) .clip(RoundedCornerShape(8.dp)) .noRippleClickable(onClick = onClick), ) { - AsyncImage( - modifier = Modifier - .cardShadow(shape = RoundedCornerShape(8.dp)) - .matchParentSize() - .hazeSource(hazeState) - .then( - if (!thumbnailImage.isNullOrBlank()) Modifier - else Modifier.background(color = NekiTheme.colorScheme.gray50), - ), - model = thumbnailImage, - contentDescription = null, - contentScale = ContentScale.Crop, - ) + if (photoCount == 0 || thumbnailImage == null) { + Box( + modifier = Modifier + .matchParentSize() + .hazeSource(hazeState) + .background(NekiTheme.colorScheme.gray25), + ) { + Icon( + modifier = Modifier + .size(40.dp) + .align(Alignment.TopCenter) + .padding(top = 38.dp), + imageVector = ImageVector.vectorResource(R.drawable.icon_empty_gallery_thumbnail), + tint = NekiTheme.colorScheme.gray100, + contentDescription = null, + ) + } + } else { + AsyncImage( + modifier = Modifier + .cardShadow(shape = RoundedCornerShape(8.dp)) + .background(color = Color.Black.copy(alpha = 0.04f)) + .matchParentSize() + .hazeSource(hazeState), + model = thumbnailImage, + contentDescription = null, + contentScale = ContentScale.Crop, + ) + } AlbumFolder( modifier = Modifier.align(Alignment.BottomCenter), hazeState = hazeState, @@ -284,10 +301,19 @@ private fun AlbumFolderLayout( @Composable private fun FavoriteAlbumItemPreview() { NekiTheme { - Box(modifier = Modifier.padding(8.dp)) { + Row( + modifier = Modifier.padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + ArchiveAlbumItem( + isFavorite = true, + title = "즐겨찾기", + photoCount = 0, + ) ArchiveAlbumItem( isFavorite = true, title = "네키 화이팅화이팅", + thumbnailImage = "https://example.com/photo.jpg", photoCount = 10, ) } @@ -298,9 +324,17 @@ private fun FavoriteAlbumItemPreview() { @Composable private fun ArchiveAlbumItemPreview() { NekiTheme { - Box(modifier = Modifier.padding(8.dp)) { + Row( + modifier = Modifier.padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + ArchiveAlbumItem( + title = "빈 앨범", + photoCount = 0, + ) ArchiveAlbumItem( title = "네키 화이팅화이팅", + thumbnailImage = "https://example.com/photo.jpg", photoCount = 10, ) } From f0629497a35d9eb46c9314c9eae6d68c5579eccf Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:02:28 +0900 Subject: [PATCH 16/30] =?UTF-8?q?[design]=20#108:=20=EC=83=88=20=EC=95=A8?= =?UTF-8?q?=EB=B2=94=20=EC=B6=94=EA=B0=80=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/designsystem/modifier/Stroke.kt | 30 +++++++++++ .../main/component/ArchiveMainAlbumList.kt | 52 ++++++++++++++++++- 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Stroke.kt diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Stroke.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Stroke.kt new file mode 100644 index 000000000..d9940d3d3 --- /dev/null +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/modifier/Stroke.kt @@ -0,0 +1,30 @@ +package com.neki.android.core.designsystem.modifier + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +fun Modifier.dashStroke( + color: Color, + strokeWidth: Dp = 1.dp, + dashLength: Dp = 5.dp, + gapLength: Dp = 5.dp, + cornerRadius: Dp = 0.dp, +): Modifier = this.drawBehind { + drawRoundRect( + color = color, + style = Stroke( + width = strokeWidth.toPx(), + pathEffect = PathEffect.dashPathEffect( + intervals = floatArrayOf(dashLength.toPx(), gapLength.toPx()), + phase = 0f, + ), + ), + cornerRadius = CornerRadius(cornerRadius.toPx()), + ) +} diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt index bdb302d5b..ce133d50e 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt @@ -40,9 +40,12 @@ import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.R import com.neki.android.core.designsystem.modifier.backgroundHazeBlur import com.neki.android.core.designsystem.modifier.cardShadow +import com.neki.android.core.designsystem.modifier.dashStroke import com.neki.android.core.designsystem.modifier.noRippleClickable import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.core.model.AlbumPreview +import com.neki.android.feature.archive.impl.const.ArchiveConst.ARCHIVE_ALBUM_ITEM_HEIGHT +import com.neki.android.feature.archive.impl.const.ArchiveConst.ARCHIVE_ALBUM_ITEM_WIDTH import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.rememberHazeState @@ -153,6 +156,43 @@ internal fun ArchiveMainAlbumList( } } +@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, + ) + } + } +} + @Composable private fun ArchiveAlbumItem( title: String, @@ -167,7 +207,7 @@ private fun ArchiveAlbumItem( Box( modifier = modifier .cardShadow() - .height(166.dp) + .height(ARCHIVE_ALBUM_ITEM_HEIGHT.dp) .clip(RoundedCornerShape(8.dp)) .noRippleClickable(onClick = onClick), ) { @@ -219,7 +259,7 @@ private fun AlbumFolder( isFavorite: Boolean = false, ) { AlbumFolderLayout( - modifier = modifier.width(124.dp), + modifier = modifier.width(ARCHIVE_ALBUM_ITEM_WIDTH.dp), hazeState = hazeState, color = if (isFavorite) NekiTheme.colorScheme.favoriteAlbumCover else NekiTheme.colorScheme.defaultAlbumCover, @@ -297,6 +337,14 @@ private fun AlbumFolderLayout( ) } +@ComponentPreview +@Composable +private fun AddAlbumItemPreview() { + NekiTheme { + AddAlbumItem() + } +} + @ComponentPreview @Composable private fun FavoriteAlbumItemPreview() { From 02b9f38afab0a431abbc64028cbbddcaffc21fb0 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:25:38 +0900 Subject: [PATCH 17/30] =?UTF-8?q?[feat]=20#108:=20=EA=B3=B5=EC=9A=A9=20Dro?= =?UTF-8?q?pdownPopup=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 공통으로 사용할 수 있는 `DropdownPopup` 컴포넌트를 `core/ui` 모듈에 추가했습니다. 기존 `AllPhotoFilterBar`에서 사용하던 Popup 로직을 새로운 공용 컴포넌트로 대체하여 재사용성을 높였습니다. --- .../core/ui/component/DropdownPopup.kt | 87 +++++++++++++++++++ .../impl/photo/component/AllPhotoFilterBar.kt | 71 +++------------ 2 files changed, 100 insertions(+), 58 deletions(-) create mode 100644 core/ui/src/main/java/com/neki/android/core/ui/component/DropdownPopup.kt diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/DropdownPopup.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/DropdownPopup.kt new file mode 100644 index 000000000..934688057 --- /dev/null +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/DropdownPopup.kt @@ -0,0 +1,87 @@ +package com.neki.android.core.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.modifier.clickableSingle +import com.neki.android.core.designsystem.modifier.dropdownShadow +import com.neki.android.core.designsystem.ui.theme.NekiTheme +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +@Composable +fun DropdownPopup( + items: ImmutableList, + selectedItem: T?, + onSelect: (T) -> Unit, + onDismissRequest: () -> Unit, + itemLabel: (T) -> String, + modifier: Modifier = Modifier, + offset: IntOffset = IntOffset.Zero, + alignment: Alignment = Alignment.TopStart, +) { + Popup( + offset = offset, + alignment = alignment, + onDismissRequest = onDismissRequest, + properties = PopupProperties(focusable = true), + ) { + Column( + modifier = modifier + .dropdownShadow(shape = RoundedCornerShape(8.dp)) + .background( + color = NekiTheme.colorScheme.white, + shape = RoundedCornerShape(8.dp), + ) + .padding(vertical = 6.dp), + ) { + items.forEach { item -> + Text( + modifier = Modifier + .fillMaxWidth() + .background( + color = if (selectedItem == item) NekiTheme.colorScheme.gray50 + else NekiTheme.colorScheme.white, + ) + .clickableSingle { onSelect(item) } + .padding(horizontal = 16.dp, vertical = 5.dp), + text = itemLabel(item), + style = NekiTheme.typography.body14Medium, + ) + } + } + } +} + +private enum class PreviewDropdownOption(val label: String) { + NEWEST("최신순"), + OLDEST("오래된순"), + ; + + override fun toString(): String = label +} + +@ComponentPreview +@Composable +private fun DropdownPopupPreview() { + NekiTheme { + DropdownPopup( + items = PreviewDropdownOption.entries.toImmutableList(), + selectedItem = PreviewDropdownOption.NEWEST, + onSelect = {}, + onDismissRequest = {}, + itemLabel = { it.label }, + ) + } +} diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoFilterBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoFilterBar.kt index b7bded7f7..59ca2dd33 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoFilterBar.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoFilterBar.kt @@ -1,31 +1,22 @@ package com.neki.android.feature.archive.impl.photo.component -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Popup -import androidx.compose.ui.window.PopupProperties import com.neki.android.core.designsystem.ComponentPreview -import com.neki.android.core.designsystem.modifier.clickableSingle -import com.neki.android.core.designsystem.modifier.dropdownShadow import com.neki.android.core.designsystem.ui.theme.NekiTheme +import com.neki.android.core.ui.component.DropdownPopup import com.neki.android.core.ui.component.FilterBar import com.neki.android.feature.archive.impl.photo.PhotoFilter +import kotlinx.collections.immutable.toImmutableList @Composable internal fun AllPhotoFilterBar( @@ -51,59 +42,23 @@ internal fun AllPhotoFilterBar( onClickDefaultChip = onClickFavoriteChip, ) if (showFilterPopup) { - PhotoFilterPopup( - selectedFilter = selectedFilter, + val density = LocalDensity.current + val popupOffsetX = with(density) { 20.dp.toPx().toInt() } + val popupOffsetY = with(density) { 46.dp.toPx().toInt() } + + DropdownPopup( + items = PhotoFilter.entries.toImmutableList(), + selectedItem = selectedFilter, + onSelect = onClickFilterRow, onDismissRequest = onDismissPopup, - onClickFilterRow = onClickFilterRow, + itemLabel = { it.label }, + modifier = Modifier.width(96.dp), + offset = IntOffset(x = popupOffsetX, y = popupOffsetY), ) } } } -@Composable -private fun PhotoFilterPopup( - selectedFilter: PhotoFilter, - onDismissRequest: () -> Unit, - onClickFilterRow: (PhotoFilter) -> Unit, -) { - val density = LocalDensity.current - val popupOffsetX = with(density) { 20.dp.toPx().toInt() } - val popupOffsetY = with(density) { 46.dp.toPx().toInt() } - - Popup( - offset = IntOffset(x = popupOffsetX, y = popupOffsetY), - alignment = Alignment.TopStart, - onDismissRequest = onDismissRequest, - properties = PopupProperties(focusable = true), - ) { - Column( - modifier = Modifier - .dropdownShadow(shape = RoundedCornerShape(8.dp)) - .background( - color = NekiTheme.colorScheme.white, - shape = RoundedCornerShape(8.dp), - ) - .width(96.dp) - .padding(vertical = 6.dp), - ) { - PhotoFilter.entries.forEach { filter -> - Text( - modifier = Modifier - .fillMaxWidth() - .background( - color = if (selectedFilter == filter) NekiTheme.colorScheme.gray50 - else NekiTheme.colorScheme.white, - ) - .clickableSingle { onClickFilterRow(filter) } - .padding(horizontal = 16.dp, vertical = 5.dp), - text = filter.label, - style = NekiTheme.typography.body14Medium, - ) - } - } - } -} - @ComponentPreview @Composable private fun AllPhotoFilterBarDefaultPreview() { From 39e1d128b519c39d56e34b3c244d775c7a1a7420 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:26:00 +0900 Subject: [PATCH 18/30] =?UTF-8?q?[design]=20PhotoActionBar=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=ED=99=9C=EC=84=B1=ED=99=94=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=EB=B3=80=EA=B2=BD=20#108?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 보관함의 사진 상세 화면에서 다운로드 및 삭제 아이콘의 활성화 시 색상을 `gray600`에서 `gray700`으로 변경했습니다. --- .../archive/impl/photo/component/PhotoActionBar.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/PhotoActionBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/PhotoActionBar.kt index 1f8e2e518..d085a7875 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/PhotoActionBar.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/PhotoActionBar.kt @@ -44,7 +44,8 @@ internal fun PhotoActionBar( ), imageVector = ImageVector.vectorResource(R.drawable.icon_download), contentDescription = null, - tint = if (isEnabled) NekiTheme.colorScheme.gray600 else NekiTheme.colorScheme.gray200, + tint = if (isEnabled) NekiTheme.colorScheme.gray700 + else NekiTheme.colorScheme.gray200, ) }, endContent = { @@ -57,7 +58,8 @@ internal fun PhotoActionBar( ), imageVector = ImageVector.vectorResource(R.drawable.icon_trash), contentDescription = null, - tint = if (isEnabled) NekiTheme.colorScheme.gray600 else NekiTheme.colorScheme.gray200, + tint = if (isEnabled) NekiTheme.colorScheme.gray700 + else NekiTheme.colorScheme.gray200, ) }, ) @@ -81,7 +83,7 @@ private fun PhotoActionBarDisabledPreview() { NekiTheme { PhotoActionBar( visible = true, - isEnabled = true, + isEnabled = false, ) } } From 0d4e0f11315791c1dbdbbcc66080ad3028de9b2c Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:32:10 +0900 Subject: [PATCH 19/30] =?UTF-8?q?[refactor]=20#108:=20=EC=95=84=EC=B9=B4?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=20=EC=95=A8=EB=B2=94=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=ED=85=9C=20Modifier=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `background`와 `hazeSource`의 순서를 변경하여 잠재적인 UI 문제를 해결하고, 미리보기 컴포저블에 패딩을 추가하여 레이아웃을 개선했습니다. --- .../impl/main/component/ArchiveMainAlbumList.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt index ce133d50e..61d3241d5 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt @@ -215,14 +215,14 @@ private fun ArchiveAlbumItem( Box( modifier = Modifier .matchParentSize() - .hazeSource(hazeState) - .background(NekiTheme.colorScheme.gray25), + .background(NekiTheme.colorScheme.gray25) + .hazeSource(hazeState), ) { Icon( modifier = Modifier + .padding(top = 38.dp) .size(40.dp) - .align(Alignment.TopCenter) - .padding(top = 38.dp), + .align(Alignment.TopCenter), imageVector = ImageVector.vectorResource(R.drawable.icon_empty_gallery_thumbnail), tint = NekiTheme.colorScheme.gray100, contentDescription = null, @@ -341,7 +341,9 @@ private fun AlbumFolderLayout( @Composable private fun AddAlbumItemPreview() { NekiTheme { - AddAlbumItem() + Box(modifier = Modifier.padding(8.dp)) { + AddAlbumItem() + } } } From 9989d214ebf7e241e13a14f4c0491bbf8c69eec5 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:50:29 +0900 Subject: [PATCH 20/30] =?UTF-8?q?[design]=20#108:=20ToolTip=20=EB=8B=AB?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../neki/android/core/designsystem/popup/ToolTipPopup.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt index 4e59fa71a..f3c402875 100644 --- a/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.R +import com.neki.android.core.designsystem.modifier.noRippleClickableSingle import com.neki.android.core.designsystem.ui.theme.NekiTheme private const val DEFAULT_ARROW_POSITION = 16 @@ -51,6 +52,7 @@ fun ToolTipPopup( toolTipColor: ToolTipColor = ToolTipColor.Gray800, hasCloseButton: Boolean = false, onDismissRequest: () -> Unit = {}, + onClickCloseButton: () -> Unit = {}, ) { Popup( alignment = alignment, @@ -64,6 +66,7 @@ fun ToolTipPopup( arrowPosition = arrowPosition, arrowAlignment = arrowAlignment, hasCloseButton = hasCloseButton, + onClickCloseButton = onClickCloseButton, ) } } @@ -110,6 +113,7 @@ private fun ToolTipContent( toolTipColor: ToolTipColor = ToolTipColor.Gray800, arrowPosition: Dp = DEFAULT_ARROW_POSITION.dp, hasCloseButton: Boolean = false, + onClickCloseButton: () -> Unit = {}, ) { val backgroundColor = when (toolTipColor) { ToolTipColor.Gray800 -> NekiTheme.colorScheme.gray800 @@ -149,7 +153,9 @@ private fun ToolTipContent( ) if (hasCloseButton) { Icon( - modifier = Modifier.size(16.dp), + modifier = Modifier + .size(16.dp) + .noRippleClickableSingle(onClick = onClickCloseButton), imageVector = ImageVector.vectorResource(R.drawable.icon_close), tint = iconTint, contentDescription = null, From 5145ed555ebef59b46e6a692f7f8f9afaa237c3d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:50:33 +0900 Subject: [PATCH 21/30] =?UTF-8?q?[design]=20#108:=20=EC=95=A8=EB=B2=94=20?= =?UTF-8?q?=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EB=B9=88=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EB=B3=B4=EC=99=84=20=EB=B0=8F=20clip=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/neki/android/core/ui/component/AlbumRowComponent.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt index 949f8b186..f5c326e17 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/AlbumRowComponent.kt @@ -146,10 +146,11 @@ private fun AlbumThumbnail( isEmpty: Boolean, modifier: Modifier = Modifier, ) { - if (isEmpty) { + if (isEmpty || thumbnailUrl == null) { Box( modifier = Modifier .size(72.dp) + .clip(RoundedCornerShape(8.dp)) .background(NekiTheme.colorScheme.gray50), contentAlignment = Alignment.Center, ) { From 1ab356a33b2ea2c2da7402810aa21cde5673cb02 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:50:34 +0900 Subject: [PATCH 22/30] =?UTF-8?q?[fix]=20#108:=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4=20border=20shape=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/neki/android/core/ui/component/ItemOverlay.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt index 5d0d70ddb..42192cde4 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt @@ -47,7 +47,7 @@ fun SelectedPhotoGridItemOverlay( .border( width = 2.dp, color = NekiTheme.colorScheme.primary400, - shape = RoundedCornerShape(8.dp), + shape = shape, ) .background( color = Color.Black.copy(alpha = 0.2f), From eb6b42c3cd0e8f2c3591c8606ae8b68d563f5e1d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:50:35 +0900 Subject: [PATCH 23/30] =?UTF-8?q?[design]=20#108:=20=EB=B9=88=20=EC=95=A8?= =?UTF-8?q?=EB=B2=94=20=EC=95=88=EB=82=B4=20=EB=AC=B8=EA=B5=AC=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/feature/archive/impl/component/EmptyAlbumContent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyAlbumContent.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyAlbumContent.kt index 7b644647f..2bf38eab3 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyAlbumContent.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyAlbumContent.kt @@ -26,7 +26,7 @@ internal fun EmptyAlbumContent( EmptyContent( modifier = Modifier.align(Alignment.Center), - emptyText = "아직 등록된 사진이 없어요\n새로운 사진을 등록하고 앨범에 추가해보세요!", + emptyText = "아직 등록된 사진이 없어요\n찍은 네컷을 저장해보세요!", ) } } From 875ad8c86a8796f0b35e65c1430a0103af22ed47 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:50:36 +0900 Subject: [PATCH 24/30] =?UTF-8?q?[design]=20#108:=20=EC=95=A8=EB=B2=94=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20car?= =?UTF-8?q?dShadow=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/archive/impl/main/component/ArchiveMainAlbumList.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt index 61d3241d5..5d5d7cec9 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt @@ -231,7 +231,6 @@ private fun ArchiveAlbumItem( } else { AsyncImage( modifier = Modifier - .cardShadow(shape = RoundedCornerShape(8.dp)) .background(color = Color.Black.copy(alpha = 0.04f)) .matchParentSize() .hazeSource(hazeState), From 31497e7c5ec52f2fde5c1b7789a69979f59f0a5a Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sun, 15 Feb 2026 03:50:37 +0900 Subject: [PATCH 25/30] =?UTF-8?q?[refactor]=20#108:=20RecommendationChip?= =?UTF-8?q?=20Modifier=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/pose/impl/main/component/RecommendationChip.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt index 5025c3093..728975cdf 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/main/component/RecommendationChip.kt @@ -28,8 +28,8 @@ internal fun RecommendationChip( ) { Row( modifier = modifier - .clip(CircleShape) .buttonShadow() + .clip(CircleShape) .clickableSingle(onClick = onClick) .background(shape = CircleShape, color = NekiTheme.colorScheme.gray800) .padding(horizontal = 16.dp, vertical = 8.dp), From 5297d74ea389f160ccefe1ddbdc49c038c6a4ffc Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:26:02 +0900 Subject: [PATCH 26/30] =?UTF-8?q?[chore]=20#108:=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EA=B8=80=EC=9E=90=EC=88=98=20=EC=A0=9C=ED=95=9C=20?= =?UTF-8?q?12=EC=9E=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/neki/android/feature/mypage/impl/const/MyPageConst.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/const/MyPageConst.kt b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/const/MyPageConst.kt index 6af7f4363..2306c71d6 100644 --- a/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/const/MyPageConst.kt +++ b/feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/const/MyPageConst.kt @@ -1,5 +1,5 @@ package com.neki.android.feature.mypage.impl.const internal object MyPageConst { - internal const val NICKNAME_MAX_LENGTH = 10 + internal const val NICKNAME_MAX_LENGTH = 12 } From e61e6642f3051c047b70f56a08ed5ecb420d10a4 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:26:13 +0900 Subject: [PATCH 27/30] =?UTF-8?q?[design]=20#108:=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=99=94=EB=A9=B4=20=EC=95=A1=EC=85=98?= =?UTF-8?q?=EB=B0=94=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사진 상세 화면의 액션바에서 다운로드 및 삭제 아이콘의 색상을 `gray600`에서 `gray700`으로 변경했습니다. --- .../component/PhotoDetailActionBar.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt index 170d80342..4d5320798 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt @@ -1,5 +1,6 @@ package com.neki.android.feature.archive.impl.photo_detail.component +import android.R.attr.contentDescription import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -38,24 +39,20 @@ internal fun PhotoDetailActionBar( .noRippleClickable { onClickDownload() }, imageVector = ImageVector.vectorResource(R.drawable.icon_download), contentDescription = null, - tint = NekiTheme.colorScheme.gray500, + tint = NekiTheme.colorScheme.gray700, ) Icon( modifier = Modifier .size(28.dp) .noRippleClickable { onClickFavorite() }, - imageVector = if (isFavorite) { - ImageVector.vectorResource(R.drawable.icon_heart_filled) - } else { - ImageVector.vectorResource(R.drawable.icon_heart_stroked) - }, + imageVector = ImageVector.vectorResource( + if (isFavorite) R.drawable.icon_heart_filled + else R.drawable.icon_heart_stroked, + ), contentDescription = null, - tint = if (isFavorite) { - NekiTheme.colorScheme.primary400 - } else { - NekiTheme.colorScheme.gray700 - }, + tint = if (isFavorite) NekiTheme.colorScheme.primary400 + else NekiTheme.colorScheme.gray700, ) } }, @@ -67,7 +64,7 @@ internal fun PhotoDetailActionBar( .noRippleClickable { onClickDelete() }, imageVector = ImageVector.vectorResource(R.drawable.icon_trash), contentDescription = null, - tint = NekiTheme.colorScheme.gray600, + tint = NekiTheme.colorScheme.gray700, ) }, ) From 5917d82d9f50e886ee4c035b3753bdf7f266140b Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:26:22 +0900 Subject: [PATCH 28/30] =?UTF-8?q?[design]=20#108:=20NekiActionBar=20?= =?UTF-8?q?=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=ED=81=AC=EA=B8=B0=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `NekiActionBar` 컴포저블의 뒤로가기 버튼 아이콘(`icon_arrow_left`) 크기를 28dp로 설정하여 디자인을 수정했습니다. --- .../neki/android/core/designsystem/actionbar/NekiActionBar.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt index 124eefa16..1960f1a62 100644 --- a/core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/actionbar/NekiActionBar.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.runtime.Composable @@ -100,6 +101,7 @@ private fun NekiStartActionBarPreview() { onClick = {}, ) { Icon( + modifier = Modifier.size(28.dp), imageVector = ImageVector.vectorResource(R.drawable.icon_arrow_left), contentDescription = null, tint = NekiTheme.colorScheme.gray900, From 7c756dc2955fed6cd16d47ca92bb85564742cefd Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:26:33 +0900 Subject: [PATCH 29/30] =?UTF-8?q?[design]=20#108:=20cardShadow=EC=97=90=20?= =?UTF-8?q?shape=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cardShadow` Modifier에 `shape` 파라미터를 추가하여 그림자 모양을 카드 모양과 일치하도록 수정했습니다. --- .../feature/archive/impl/main/component/ArchiveMainAlbumList.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt index 5d5d7cec9..65faf8287 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainAlbumList.kt @@ -206,7 +206,7 @@ private fun ArchiveAlbumItem( Box( modifier = modifier - .cardShadow() + .cardShadow(shape = RoundedCornerShape(8.dp)) .height(ARCHIVE_ALBUM_ITEM_HEIGHT.dp) .clip(RoundedCornerShape(8.dp)) .noRippleClickable(onClick = onClick), From c52e1a8cf3df8b2d506bac05107396236c296e1b Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:40:36 +0900 Subject: [PATCH 30/30] =?UTF-8?q?[chore]=20#108:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=20=EB=AC=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `PhotoDetailActionBar.kt` 파일에서 사용되지 않는 `android.R.attr.contentDescription` import 문을 제거했습니다. --- .../archive/impl/photo_detail/component/PhotoDetailActionBar.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt index 4d5320798..22d7759bf 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo_detail/component/PhotoDetailActionBar.kt @@ -1,6 +1,5 @@ package com.neki.android.feature.archive.impl.photo_detail.component -import android.R.attr.contentDescription import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth