From 4d7b4f7b7e1635ee4b249b2a8c7699c4d2493423 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:03:31 +0900 Subject: [PATCH 01/24] =?UTF-8?q?[refactor]=20#78:=20=EC=95=84=EC=B9=B4?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=20=EC=95=A8=EB=B2=94=20LazyColumn=EC=9D=98?= =?UTF-8?q?=20verticalArrangement=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 앨범 전체 보기 화면의 `LazyColumn`에서 `verticalArrangement` 속성을 제거하여 아이템 간의 간격을 조정했습니다. --- .../neki/android/feature/archive/impl/album/AllAlbumScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumScreen.kt index 2648fc37a..128007df8 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumScreen.kt @@ -90,7 +90,6 @@ internal fun AllAlbumScreen( LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(20.dp), ) { item { FavoriteAlbumRowComponent( From c3120b538bae8e3f57338c16b9e9a307dcf73f62 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:04:38 +0900 Subject: [PATCH 02/24] =?UTF-8?q?[chore]=20#78:=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EB=8A=94=20=EC=82=AC=EC=A7=84=20=EC=95=A8=EB=B2=94?= =?UTF-8?q?=EB=AA=85=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/neki/android/core/data/remote/api/PhotoService.kt | 2 +- .../com/neki/android/core/ui/component/AlbumRowComponent.kt | 4 ++-- .../android/feature/archive/impl/album/AllAlbumScreen.kt | 5 ++--- .../feature/archive/impl/album_detail/AlbumDetailScreen.kt | 2 +- .../android/feature/archive/impl/main/ArchiveMainScreen.kt | 4 ++-- .../archive/impl/main/component/ArchiveMainAlbumList.kt | 2 +- .../feature/photo_upload/impl/album/UploadAlbumScreen.kt | 4 ++-- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/core/data/src/main/java/com/neki/android/core/data/remote/api/PhotoService.kt b/core/data/src/main/java/com/neki/android/core/data/remote/api/PhotoService.kt index 051d14236..2ef868aec 100644 --- a/core/data/src/main/java/com/neki/android/core/data/remote/api/PhotoService.kt +++ b/core/data/src/main/java/com/neki/android/core/data/remote/api/PhotoService.kt @@ -54,7 +54,7 @@ class PhotoService @Inject constructor( }.body() } - // 즐겨찾기 사진 조회 + // 즐겨찾는 앨범 조회 suspend fun getFavoritePhotos( page: Int = 0, size: Int = 20, 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 606c7b9f7..c48cc51b1 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 @@ -45,7 +45,7 @@ fun FavoriteAlbumRowComponent( ) AlbumInfo( - title = "즐겨찾는 사진", + title = "즐겨찾기", photoCount = album.photoCount, ) } @@ -168,7 +168,7 @@ private fun FavoriteAlbumRowComponentPreview() { FavoriteAlbumRowComponent( album = AlbumPreview( id = 0, - title = "즐겨찾는 사진", + title = "즐겨찾기", ), ) } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumScreen.kt index 128007df8..3fa5f9545 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album/AllAlbumScreen.kt @@ -2,7 +2,6 @@ package com.neki.android.feature.archive.impl.album import androidx.activity.compose.BackHandler import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -164,7 +163,7 @@ private fun AllAlbumScreenPreview() { NekiTheme { AllAlbumScreen( uiState = AllAlbumState( - favoriteAlbum = AlbumPreview(id = 0, title = "즐겨찾는 사진", photoCount = 3), + favoriteAlbum = AlbumPreview(id = 0, title = "즐겨찾기", photoCount = 3), albums = persistentListOf( AlbumPreview(id = 1, title = "제주도 여행 2024", photoCount = 4), AlbumPreview(id = 2, title = "가족 생일파티", photoCount = 2), @@ -182,7 +181,7 @@ private fun AllAlbumScreenSelectingPreview() { NekiTheme { AllAlbumScreen( uiState = AllAlbumState( - favoriteAlbum = AlbumPreview(id = 0, title = "즐겨찾는 사진", photoCount = 3), + favoriteAlbum = AlbumPreview(id = 0, title = "즐겨찾기", photoCount = 3), albums = persistentListOf( AlbumPreview(id = 1, title = "제주도 여행 2024", photoCount = 4), AlbumPreview(id = 2, title = "가족 생일파티", photoCount = 2), diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt index aad0acc6e..23eb27bbe 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt @@ -107,7 +107,7 @@ internal fun AlbumDetailScreen( ) { AlbumDetailTopBar( hasNoPhoto = isEmpty, - title = if (uiState.isFavoriteAlbum) "즐겨찾는 사진" else uiState.title, + title = if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title, selectMode = uiState.selectMode, onClickBack = { onIntent(AlbumDetailIntent.ClickBackIcon) }, onClickSelect = { onIntent(AlbumDetailIntent.ClickSelectButton) }, 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 8f79a6d99..3c531d100 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 @@ -275,7 +275,7 @@ private fun ArchiveMainScreenPreview() { val favoriteAlbum = AlbumPreview( id = 0, - title = "즐겨찾는 사진", + title = "즐겨찾기", thumbnailUrl = "https://picsum.photos/seed/fav1/200/300", photoCount = 5, ) @@ -296,7 +296,7 @@ private fun ArchiveMainScreenPreview() { private fun ArchiveMainScreenEmptyPreview() { val favoriteAlbum = AlbumPreview( id = 0, - title = "즐겨찾는 사진", + title = "즐겨찾기", ) NekiTheme { 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 c897679e9..043f1600e 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 @@ -247,7 +247,7 @@ private fun FavoriteAlbumFolderPreview() { NekiTheme { AlbumFolder( isFavorite = true, - title = "즐겨찾는 사진", + title = "즐겨찾기", photoCount = 12, ) } diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt index 0c46218ce..d55004bd5 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt @@ -103,7 +103,7 @@ private fun UploadAlbumScreenPreview() { NekiTheme { UploadAlbumScreen( uiState = UploadAlbumState( - favoriteAlbum = AlbumPreview(id = 0, title = "즐겨찾는 사진", photoCount = 3), + favoriteAlbum = AlbumPreview(id = 0, title = "즐겨찾기", photoCount = 3), albums = persistentListOf( AlbumPreview(id = 1, title = "제주도 여행 2024", photoCount = 4), AlbumPreview(id = 2, title = "가족 생일파티", photoCount = 2), @@ -121,7 +121,7 @@ private fun UploadAlbumScreenSelectingPreview() { NekiTheme { UploadAlbumScreen( uiState = UploadAlbumState( - favoriteAlbum = AlbumPreview(id = 0, title = "즐겨찾는 사진", photoCount = 3), + favoriteAlbum = AlbumPreview(id = 0, title = "즐겨찾기", photoCount = 3), albums = persistentListOf( AlbumPreview(id = 1, title = "제주도 여행 2024", photoCount = 4), AlbumPreview(id = 2, title = "가족 생일파티", photoCount = 2), From 938970bd9a0d4b5199a31f7cdb0acfb50dc3d405 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:17:34 +0900 Subject: [PATCH 03/24] =?UTF-8?q?[feat]=20#78:=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20Lottie=20?= =?UTF-8?q?=EC=A2=85=EC=86=8D=EC=84=B1=20=EB=B0=8F=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 Lottie 애니메이션을 표시하기 위해 `lottie-compose` 라이브러리 종속성을 추가하고, `loading_animation.json` 파일을 리소스로 추가했습니다. --- core/ui/build.gradle.kts | 2 ++ core/ui/src/main/res/raw/loading_animation.json | 1 + gradle/libs.versions.toml | 3 +++ 3 files changed, 6 insertions(+) create mode 100644 core/ui/src/main/res/raw/loading_animation.json diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index d4a92c5a3..5be11ac34 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.kotlinx.collections.immutable) + implementation(libs.lottie.compose) + api(libs.coil.compose) api(libs.coil.network.okhttp) } diff --git a/core/ui/src/main/res/raw/loading_animation.json b/core/ui/src/main/res/raw/loading_animation.json new file mode 100644 index 000000000..cc841a1d7 --- /dev/null +++ b/core/ui/src/main/res/raw/loading_animation.json @@ -0,0 +1 @@ +{"nm":"Main Scene","ddd":0,"h":1080,"w":1920,"meta":{"g":"@lottiefiles/creator 1.74.0"},"layers":[{"ty":4,"nm":"Shape Layer 4","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-284,92,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[50,50,100],"t":25},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[75,75,100],"t":39},{"s":[50,50,100],"t":55}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[1142,540],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[1142,540,0],"t":25,"ti":[0,0,0],"to":[0,-6.667,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[1142,500,0],"t":39,"ti":[0,-6.667,0],"to":[0,0,0]},{"s":[1142,540,0],"t":55}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[25],"t":25},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":39},{"s":[25],"t":55}],"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[120,120],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.3373,0.2784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-284,92],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Shape Layer 3","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-284,92,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[50,50,100],"t":17},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[75,75,100],"t":31},{"s":[50,50,100],"t":47}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[1022,540],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[1022,540,0],"t":17,"ti":[0,0,0],"to":[0,-6.667,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[1022,500,0],"t":31,"ti":[0,-6.667,0],"to":[0,0,0]},{"s":[1022,540,0],"t":47}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[25],"t":17},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":31},{"s":[25],"t":47}],"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[120,120],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.3373,0.2784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-284,92],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"Shape Layer 2","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-284,92,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[50,50,100],"t":9},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[75,75,100],"t":23},{"s":[50,50,100],"t":39}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[902,540],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[902,540,0],"t":9,"ti":[0,0,0],"to":[0,-6.667,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[902,500,0],"t":23,"ti":[0,0,0],"to":[0,0,0]},{"s":[902,540,0],"t":39}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[25],"t":9},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":23},{"s":[25],"t":39}],"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[120,120],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.3373,0.2784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-284,92],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"Shape Layer 1","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-284,92,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[50,50,100],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[75,75,100],"t":14},{"s":[50,50,100],"t":30}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[782,540],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[782,500,0],"t":14,"ti":[0,-6.667,0],"to":[0,0,0]},{"s":[782,540,0],"t":30}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[25],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":14},{"s":[25],"t":30}],"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[120,120],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.3373,0.2784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-284,92],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4}],"v":"5.7.0","fr":60,"op":81,"ip":0,"assets":[]} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce6124c21..1ef1cf618 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,6 +37,7 @@ barcodeScanning = "17.3.0" kakao = "2.23.1" coil = "3.3.0" haze = "1.7.1" +lottie = "6.7.1" [libraries] androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraX" } @@ -105,6 +106,8 @@ coil-network-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp" haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" } haze-materials = { group = "dev.chrisbanes.haze", name = "haze-materials", version.ref = "haze" } +lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottie" } + androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" } # Dependencies of the included build-logic From 22480193ec83a2b38555551949e28f4f1f2fc520 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:17:46 +0900 Subject: [PATCH 04/24] =?UTF-8?q?[feat]=20#78:=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EC=9D=B8=EB=94=94=EC=BC=80=EC=9D=B4=ED=84=B0=20Lottie=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 `CircularProgressIndicator`를 사용하던 로딩 인디케이터를 Lottie 애니메이션으로 교체했습니다. --- .../core/ui/component/LoadingIndicator.kt | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/LoadingIndicator.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/LoadingIndicator.kt index c6f16c7db..89e89829b 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/LoadingIndicator.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/LoadingIndicator.kt @@ -1,20 +1,26 @@ package com.neki.android.core.ui.component -import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.compose.rememberLottieComposition import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.ui.theme.NekiTheme +import com.neki.android.core.ui.R @Composable fun LoadingDialog( modifier: Modifier = Modifier, - circleColor: Color = NekiTheme.colorScheme.primary300, - backgroundColor: Color = NekiTheme.colorScheme.primary100, + size: Dp = 200.dp, properties: DialogProperties = DialogProperties(), onDismissRequest: () -> Unit = {}, ) { @@ -22,11 +28,8 @@ fun LoadingDialog( onDismissRequest = onDismissRequest, properties = properties, ) { - CircularProgressIndicator( - modifier = modifier, - strokeWidth = 6.dp, - color = circleColor, - trackColor = backgroundColor, + LoadingIndicator( + modifier = modifier.size(size), ) } } @@ -34,14 +37,20 @@ fun LoadingDialog( @Composable fun LoadingIndicator( modifier: Modifier = Modifier, - circleColor: Color = NekiTheme.colorScheme.primary300, - backgroundColor: Color = NekiTheme.colorScheme.primary100, + size: Dp = 80.dp, ) { - CircularProgressIndicator( - modifier = modifier, - strokeWidth = 6.dp, - color = circleColor, - trackColor = backgroundColor, + val composition by rememberLottieComposition( + spec = LottieCompositionSpec.RawRes(R.raw.loading_animation), + ) + val progress by animateLottieCompositionAsState( + composition = composition, + iterations = LottieConstants.IterateForever, + ) + + LottieAnimation( + composition = composition, + progress = { progress }, + modifier = modifier.size(size), ) } @@ -54,3 +63,11 @@ private fun LoadingDialogPreview() { ) } } + +@ComponentPreview +@Composable +private fun LoadingIndicatorPreview() { + NekiTheme { + LoadingIndicator() + } +} From 28b14ca518a45f9757ab9ad4a3a7c3b5c3dde37b Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:23:49 +0900 Subject: [PATCH 05/24] =?UTF-8?q?[feat]=20#78:=20=EB=84=A4=ED=82=A4=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=ED=8F=AC=20=EB=A1=9C=EA=B3=A0=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 네키 타이포 로고(Neki Typo Logo) 벡터 아이콘과 이를 사용하는 컴포저블 함수(`PrimaryNekiTypoLogo`, `GrayNekiTypoLogo`, `WhiteNekiTypoLogo`)를 추가했습니다. --- .../core/designsystem/logo/NekiTypoLogo.kt | 79 +++++++++++++++++++ .../main/res/drawable/icon_neki_logo_typo.xml | 18 +++++ .../impl/main/component/ArchiveMainTopBar.kt | 8 +- 3 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 core/designsystem/src/main/java/com/neki/android/core/designsystem/logo/NekiTypoLogo.kt create mode 100644 core/designsystem/src/main/res/drawable/icon_neki_logo_typo.xml diff --git a/core/designsystem/src/main/java/com/neki/android/core/designsystem/logo/NekiTypoLogo.kt b/core/designsystem/src/main/java/com/neki/android/core/designsystem/logo/NekiTypoLogo.kt new file mode 100644 index 000000000..a3fe50d5c --- /dev/null +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/logo/NekiTypoLogo.kt @@ -0,0 +1,79 @@ +package com.neki.android.core.designsystem.logo + +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +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.tooling.preview.Preview +import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.R +import com.neki.android.core.designsystem.ui.theme.NekiTheme + +@Composable +private fun NekiTypoLogo( + color: Color, + modifier: Modifier = Modifier, +) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.icon_neki_logo_typo), + contentDescription = null, + tint = color, + modifier = modifier, + ) +} + +@Composable +fun PrimaryNekiTypoLogo( + modifier: Modifier = Modifier, +) { + NekiTypoLogo( + color = NekiTheme.colorScheme.primary400, + modifier = modifier, + ) +} + +@Composable +fun GrayNekiTypoLogo( + modifier: Modifier = Modifier, +) { + NekiTypoLogo( + color = NekiTheme.colorScheme.gray900, + modifier = modifier, + ) +} + +@Composable +fun WhiteNekiTypoLogo( + modifier: Modifier = Modifier, +) { + NekiTypoLogo( + color = NekiTheme.colorScheme.white, + modifier = modifier, + ) +} + +@ComponentPreview +@Composable +private fun PrimaryNekiTypoLogoPreview() { + NekiTheme { + PrimaryNekiTypoLogo() + } +} + +@ComponentPreview +@Composable +private fun GrayNekiTypoLogoPreview() { + NekiTheme { + GrayNekiTypoLogo() + } +} + +@Preview +@Composable +private fun WhiteNekiTypoLogoPreview() { + NekiTheme { + WhiteNekiTypoLogo() + } +} diff --git a/core/designsystem/src/main/res/drawable/icon_neki_logo_typo.xml b/core/designsystem/src/main/res/drawable/icon_neki_logo_typo.xml new file mode 100644 index 000000000..2a701695d --- /dev/null +++ b/core/designsystem/src/main/res/drawable/icon_neki_logo_typo.xml @@ -0,0 +1,18 @@ + + + + + + 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 0533bfee6..d2a2f00ac 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 @@ -30,6 +30,7 @@ import androidx.compose.ui.window.PopupProperties 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.logo.PrimaryNekiTypoLogo import com.neki.android.core.designsystem.modifier.dropdownShadow import com.neki.android.core.designsystem.topbar.NekiLeftTitleTopBar import com.neki.android.core.designsystem.ui.theme.NekiTheme @@ -50,12 +51,7 @@ internal fun ArchiveMainTopBar( NekiLeftTitleTopBar( modifier = modifier, title = { - Box( - modifier = Modifier - .height(28.dp) - .width(56.dp) - .background(color = Color(0xFFB7B9C3)), - ) + PrimaryNekiTypoLogo() }, actions = { Row( From 1ec67f484731ca134044fbed7e5b514521e0d760 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:09:53 +0900 Subject: [PATCH 06/24] =?UTF-8?q?[refactor]=20#78:=20=EC=95=A8=EB=B2=94=20?= =?UTF-8?q?=EC=BB=A4=EB=B2=84=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EB=B0=8F=20=EB=B8=94=EB=9F=AC=20=ED=9A=A8=EA=B3=BC?= =?UTF-8?q?(Haze)=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 아카이브 화면의 앨범 커버 디자인을 개선하고, `dev.chrisbanes.haze` 라이브러리를 사용해 블러 효과를 적용했습니다. - 앨범 커버를 `drawBehind` 대신 `Shape`으로 구현하여 재사용성을 높이고, `clip`을 사용하도록 변경 - `backgroundHazeBlur` Modifier 확장 함수를 추가하여 블러 효과를 공통으로 처리 - `AlbumFolderLayout`에 `Haze`를 적용하고, `RandomPoseTutorialOverlay`에도 이를 재사용하여 적용 - 앨범 커버 내 아이콘, 텍스트 스타일 및 레이아웃 등 전반적인 UI 디테일 수정 - `PhotoTitleRow` 파일명을 `ArchiveMainTitleRow`로 변경하여 명확성 개선 --- .../core/designsystem/modifier/Background.kt | 19 +- .../main/component/ArchiveMainAlbumList.kt | 179 +++++++++++------- ...hotoTitleRow.kt => ArchiveMainTitleRow.kt} | 2 +- .../component/RandomPoseTutorialOverlay.kt | 12 +- 4 files changed, 130 insertions(+), 82 deletions(-) rename feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/{PhotoTitleRow.kt => ArchiveMainTitleRow.kt} (97%) 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 dfb5ffbea..df95109ae 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 @@ -2,13 +2,12 @@ package com.neki.android.core.designsystem.modifier import androidx.compose.foundation.background import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable 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.RectangleShape import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.luminance import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import dev.chrisbanes.haze.HazeState @@ -44,23 +43,25 @@ fun Modifier.photoBackground( * @param defaultBackgroundColor 블러 비활성화 시 적용될 기본 배경 색상 * @param blurRadius 블러 효과의 반경 */ -@Composable fun Modifier.backgroundHazeBlur( hazeState: HazeState, + alpha: Float, + color: Color, + blurRadius: Dp, enabled: Boolean = true, - color: Color = Color(0xFF202227).copy(alpha = 0.9f), defaultBackgroundColor: Color = color, - blurRadius: Dp = 12.dp, + shape: Shape = RectangleShape, ): Modifier = if (enabled) { this.hazeEffect( state = hazeState, style = HazeStyle( backgroundColor = color, - tint = HazeTint( - color.copy(alpha = if (color.luminance() >= 0.5) 0.6f else 0.65f), - ), + tint = HazeTint(color.copy(alpha = alpha)), blurRadius = blurRadius, ), ) - } else this.background(color = defaultBackgroundColor) + } else this.background( + color = defaultBackgroundColor, + shape = shape, + ) 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 043f1600e..f812bff67 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 @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -22,31 +23,100 @@ 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.draw.drawBehind -import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.drawscope.Fill -import androidx.compose.ui.graphics.drawscope.scale +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp 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.backgroundHazeBlur import com.neki.android.core.designsystem.modifier.cardShadow 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 dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.hazeSource +import dev.chrisbanes.haze.rememberHazeState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList private const val VIEWPORT_W = 124f private const val VIEWPORT_H = 65f +private val AlbumFolderShape = object : Shape { + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density, + ): Outline { + val scaleX = size.width / VIEWPORT_W + val scaleY = size.height / VIEWPORT_H + + val path = Path().apply { + moveTo(124f * scaleX, 57f * scaleY) + cubicTo(124f * scaleX, 61.42f * scaleY, 120.42f * scaleX, 65f * scaleY, 116f * scaleX, 65f * scaleY) + lineTo(8f * scaleX, 65f * scaleY) + cubicTo( + 3.58f * scaleX, + 65f * scaleY, + 0f, + 61.42f * scaleY, + 0f, + 57f * scaleY, + ) + lineTo(0f, 8f * scaleY) + cubicTo( + 0f, + 3.58f * scaleY, + 3.58f * scaleX, + 0f, + 8f * scaleX, + 0f, + ) + lineTo(58.54f * scaleX, 0f) + cubicTo( + 61.07f * scaleX, + 0f, + 63.45f * scaleX, + 1.2f * scaleY, + 64.96f * scaleX, + 3.23f * scaleY, + ) + lineTo(69.2f * scaleX, 8.93f * scaleY) + cubicTo( + 70.7f * scaleX, + 10.95f * scaleY, + 73.06f * scaleX, + 12.15f * scaleY, + 75.58f * scaleX, + 12.16f * scaleY, + ) + lineTo(116.04f * scaleX, 12.35f * scaleY) + cubicTo( + 120.44f * scaleX, + 12.36f * scaleY, + 124f * scaleX, + 15.94f * scaleY, + 124f * scaleX, + 20.34f * scaleY, + ) + lineTo(124f * scaleX, 57f * scaleY) + close() + } + return Outline.Generic(path) + } +} + @Composable internal fun ArchiveMainAlbumList( favoriteAlbum: AlbumPreview, @@ -87,11 +157,13 @@ internal fun ArchiveMainAlbumList( private fun ArchiveAlbumItem( title: String, photoCount: Int, - thumbnailImage: String? = null, modifier: Modifier = Modifier, + thumbnailImage: String? = null, isFavorite: Boolean = false, onClick: () -> Unit = {}, ) { + val hazeState = rememberHazeState() + Box( modifier = modifier .height(166.dp) @@ -102,6 +174,7 @@ private fun ArchiveAlbumItem( modifier = Modifier .cardShadow(shape = RoundedCornerShape(8.dp)) .matchParentSize() + .hazeSource(hazeState) .then( if (!thumbnailImage.isNullOrBlank()) Modifier else Modifier.background(color = NekiTheme.colorScheme.gray50), @@ -112,6 +185,7 @@ private fun ArchiveAlbumItem( ) AlbumFolder( modifier = Modifier.align(Alignment.BottomCenter), + hazeState = hazeState, title = title, photoCount = photoCount, isFavorite = isFavorite, @@ -124,38 +198,40 @@ private fun AlbumFolder( title: String, photoCount: Int, modifier: Modifier = Modifier, + hazeState: HazeState = rememberHazeState(), isFavorite: Boolean = false, ) { AlbumFolderLayout( modifier = modifier.width(124.dp), - color = if (isFavorite) NekiTheme.colorScheme.favoriteAlbumCover.copy(alpha = 0.9f) - else NekiTheme.colorScheme.defaultAlbumCover.copy(alpha = 0.92f), + hazeState = hazeState, + color = if (isFavorite) NekiTheme.colorScheme.favoriteAlbumCover + else NekiTheme.colorScheme.defaultAlbumCover, ) { Row( modifier = Modifier + .fillMaxWidth() .padding( - top = 15.dp, - bottom = 10.dp, + top = 10.dp, start = 10.dp, + bottom = 8.dp, end = 8.dp, ), - horizontalArrangement = Arrangement.spacedBy(10.dp), + horizontalArrangement = Arrangement.SpaceBetween, ) { Column( - modifier = Modifier.width(80.dp), + modifier = Modifier.widthIn(max = 86.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { Text( text = "${photoCount}장", - style = NekiTheme.typography.caption12Medium, + style = NekiTheme.typography.body14Medium, color = NekiTheme.colorScheme.white.copy(alpha = 0.7f), ) Text( - modifier = Modifier.widthIn(max = 80.dp), text = title, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = NekiTheme.typography.body14SemiBold, + style = NekiTheme.typography.body16SemiBold, color = NekiTheme.colorScheme.white, ) } @@ -163,7 +239,7 @@ private fun AlbumFolder( if (isFavorite) { Box( modifier = Modifier - .padding(bottom = 3.dp) + .padding(bottom = 2.dp) .align(Alignment.Bottom) .clip(CircleShape) .background( @@ -173,7 +249,7 @@ private fun AlbumFolder( .padding(4.dp), ) { Icon( - modifier = Modifier.size(8.dp), + modifier = Modifier.size(12.dp), imageVector = ImageVector.vectorResource(R.drawable.icon_heart), contentDescription = null, tint = NekiTheme.colorScheme.white, @@ -187,53 +263,30 @@ private fun AlbumFolder( @Composable private fun AlbumFolderLayout( modifier: Modifier = Modifier, + hazeState: HazeState = rememberHazeState(), color: Color = Color(0xFFFF5647), content: @Composable BoxScope.() -> Unit = {}, ) { Box( modifier = modifier - .drawBehind { - val scaleX = size.width / VIEWPORT_W - val scaleY = size.height / VIEWPORT_H - - scale(scaleX, scaleY, pivot = Offset.Zero) { - val path = Path().apply { - // 오른쪽 하단에서 시작 (코너 8만큼 위) - moveTo(124f, 57f) - // 오른쪽 하단 코너 - cubicTo(124f, 61.42f, 120.42f, 65f, 116f, 65f) - // 하단 직선 - lineTo(8f, 65f) - // 왼쪽 하단 코너 - cubicTo(3.58f, 65f, 0f, 61.42f, 0f, 57f) - // 왼쪽 직선 - lineTo(0f, 8f) - // 왼쪽 상단 코너 - cubicTo(0f, 3.58f, 3.58f, 0f, 8f, 0f) - // 상단 탭 모양 - lineTo(58.54f, 0f) - cubicTo(61.07f, 0f, 63.45f, 1.2f, 64.96f, 3.23f) - lineTo(69.2f, 8.93f) - cubicTo(70.7f, 10.95f, 73.06f, 12.15f, 75.58f, 12.16f) - lineTo(116.04f, 12.35f) - cubicTo(120.44f, 12.36f, 124f, 15.94f, 124f, 20.34f) - // 오른쪽 직선 - lineTo(124f, 57f) - close() - } - drawPath(path, color, style = Fill) - } - }, + .clip(AlbumFolderShape) + .backgroundHazeBlur( + hazeState = hazeState, + color = color, + blurRadius = 4.dp, + alpha = 0.92f, + ), content = content, ) } @ComponentPreview @Composable -private fun ArchiveAlbumItemPreview() { +private fun FavoriteAlbumItemPreview() { NekiTheme { Box(modifier = Modifier.padding(8.dp)) { ArchiveAlbumItem( + isFavorite = true, title = "네키 화이팅화이팅", photoCount = 10, ) @@ -243,34 +296,24 @@ private fun ArchiveAlbumItemPreview() { @ComponentPreview @Composable -private fun FavoriteAlbumFolderPreview() { +private fun ArchiveAlbumItemPreview() { NekiTheme { - AlbumFolder( - isFavorite = true, - title = "즐겨찾기", - photoCount = 12, - ) + Box(modifier = Modifier.padding(8.dp)) { + ArchiveAlbumItem( + title = "네키 화이팅화이팅", + photoCount = 10, + ) + } } } @ComponentPreview @Composable -private fun DefaultAlbumFolderPreview() { - NekiTheme { - AlbumFolder( - isFavorite = false, - title = "네키 화이팅화이팅", - photoCount = 10, - ) - } -} - -@Preview -@Composable private fun ArchiveMainAlbumListPreview() { NekiTheme { ArchiveMainAlbumList( favoriteAlbum = AlbumPreview(), + albumList = List(3) { AlbumPreview(id = it.toLong()) }.toImmutableList(), ) } } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/PhotoTitleRow.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt similarity index 97% rename from feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/PhotoTitleRow.kt rename to feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt index 38fb4d530..fcf2c6693 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/PhotoTitleRow.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt @@ -33,7 +33,7 @@ internal fun ArchiveMainTitleRow( ) { Text( text = title, - style = NekiTheme.typography.title20SemiBold, + style = NekiTheme.typography.title20Bold, color = NekiTheme.colorScheme.gray900, ) NekiTextButton( diff --git a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseTutorialOverlay.kt b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseTutorialOverlay.kt index c8952e9db..4146216e7 100644 --- a/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseTutorialOverlay.kt +++ b/feature/pose/impl/src/main/java/com/neki/android/feature/pose/impl/random/component/RandomPoseTutorialOverlay.kt @@ -23,7 +23,6 @@ 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.modifier.backgroundHazeBlur -import com.neki.android.core.designsystem.modifier.noRippleClickable import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.core.ui.compose.HorizontalSpacer import com.neki.android.core.ui.compose.VerticalSpacer @@ -35,15 +34,20 @@ import dev.chrisbanes.haze.rememberHazeState @Composable internal fun RandomPoseTutorialOverlay( onClickStart: () -> Unit, - hazeState: HazeState = rememberHazeState(), modifier: Modifier = Modifier, + hazeState: HazeState = rememberHazeState(), ) { Box( - modifier = modifier.noRippleClickable {}, // 터치 이벤트 소비용 + modifier = modifier, ) { Column( modifier = Modifier - .backgroundHazeBlur(hazeState) + .backgroundHazeBlur( + hazeState = hazeState, + color = Color(0xFF202227), + blurRadius = 12.dp, + alpha = 0.9f, + ) .fillMaxSize() .padding(top = 60.dp, bottom = 34.dp), ) { From b55efade40e12f11df7cb86fd490b67bed201cf5 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:33:17 +0900 Subject: [PATCH 07/24] =?UTF-8?q?[feat]=20#78:=20=EA=B3=B5=ED=86=B5=20`Too?= =?UTF-8?q?lTipPopup`=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=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 기존 `ArchiveMainTopBar` 내부에만 있던 툴팁 로직을, 디자인시스템의 범용 `ToolTipPopup` 컴포저블로 분리하고 이를 적용했습니다. --- .../core/designsystem/popup/ToolTipPopup.kt | 114 ++++++++++++++++++ .../impl/main/component/ArchiveMainTopBar.kt | 71 ++--------- 2 files changed, 123 insertions(+), 62 deletions(-) create mode 100644 core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt 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 new file mode 100644 index 000000000..ffb0845c4 --- /dev/null +++ b/core/designsystem/src/main/java/com/neki/android/core/designsystem/popup/ToolTipPopup.kt @@ -0,0 +1,114 @@ +package com.neki.android.core.designsystem.popup + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +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.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.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.ui.theme.NekiTheme + +@Composable +fun ToolTipPopup( + tooltipText: String, + color: Color, + offset: IntOffset, + alignment: Alignment, +) { + Popup( + alignment = alignment, + offset = offset, + ) { + ToolTipContent( + tooltipText = tooltipText, + color = color, + ) + } +} + +@Composable +private fun ToolTipContent( + tooltipText: String, + color: Color, + modifier: Modifier = Modifier, +) { + 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) + } + } + + // 몸통 + Box( + modifier = Modifier + .background( + color = color, + shape = RoundedCornerShape(8.dp), + ) + .padding(horizontal = 12.dp, vertical = 8.dp), + ) { + Text( + text = tooltipText, + style = NekiTheme.typography.body14Medium, + color = NekiTheme.colorScheme.white, + ) + } + } +} + +@ComponentPreview +@Composable +private fun ToolTipPopupPreview() { + NekiTheme { + Box(modifier = Modifier.padding(16.dp)) { + ToolTipContent( + tooltipText = "툴팁 메시지입니다", + 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 d2a2f00ac..0e513b807 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 @@ -1,6 +1,5 @@ package com.neki.android.feature.archive.impl.main.component -import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -9,17 +8,14 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height 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.HorizontalDivider 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.platform.LocalDensity import androidx.compose.ui.res.vectorResource @@ -32,6 +28,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.ToolTipPopup import com.neki.android.core.designsystem.topbar.NekiLeftTitleTopBar import com.neki.android.core.designsystem.ui.theme.NekiTheme import com.neki.android.feature.archive.impl.R as ArchiveR @@ -77,7 +74,7 @@ internal fun ArchiveMainTopBar( ) } if (showTooltip) { - ToolTip() + ArchiveToolTip() } } NekiIconButton( @@ -95,68 +92,18 @@ internal fun ArchiveMainTopBar( } @Composable -private fun ToolTip() { - val caretColor = NekiTheme.colorScheme.gray800 +private fun ArchiveToolTip() { val density = LocalDensity.current - val popupOffsetX = with(density) { 1.dp.toPx().toInt() } val popupOffsetY = with(density) { 47.dp.toPx().toInt() } + val offset = IntOffset(popupOffsetX, popupOffsetY) - Popup( + ToolTipPopup( + tooltipText = "버튼을 눌러 네컷을 추가할 수 있어요", + color = NekiTheme.colorScheme.gray800, + offset = offset, alignment = Alignment.TopEnd, - offset = IntOffset(popupOffsetX, popupOffsetY), - ) { - 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 = 2.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, caretColor) - } - } - - // 몸통 - Box( - modifier = Modifier - .background( - color = NekiTheme.colorScheme.gray800, - shape = RoundedCornerShape(8.dp), - ) - .padding(horizontal = 12.dp, vertical = 8.dp), - ) { - Text( - text = "버튼을 눌러 네컷을 추가할 수 있어요", - style = NekiTheme.typography.body14Medium, - color = NekiTheme.colorScheme.white, - ) - } - } - } + ) } @Composable From cb882099fe709903c25edef391f7a9036eb2f086 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:57:20 +0900 Subject: [PATCH 08/24] =?UTF-8?q?[feat]=20#78:=20=ED=8F=AC=ED=86=A0=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EC=A0=80=EB=B8=94=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=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 기존 `PhotoComponent`에 종속되어 있던 그라데이션 및 선택 효과 로직을 `PhotoGridItemOverlay`와 `SelectedPhotoGridItemOverlay` 컴포저블로 분리했습니다. 이를 통해 사진 그리드 아이템의 UI를 보다 독립적으로 관리하고 재사용성을 높였습니다. - `PhotoGridItemOverlay`, `SelectedPhotoGridItemOverlay` 컴포저블 추가 - `photoBackground` Modifier를 `photoGridBackground`로 변경하고, 그라데이션 효과를 상단에서 시작하도록 수정 - `PhotoComponent`에서 불필요한 `additionalContent` 파라미터 제거 - 아카이브 화면의 `SelectablePhotoItem`과 `ArchiveMainPhotoItem`에 새로운 오버레이 컴포저블 적용 --- .../core/designsystem/modifier/Background.kt | 17 ++--- .../android/core/ui/component/ItemOverlay.kt | 48 ++++++++++++ .../core/ui/component/PhotoComponent.kt | 4 - .../impl/component/SelcetablePhotoItem.kt | 75 +++++++++++-------- .../main/component/ArchiveMainPhotoItem.kt | 20 +++-- 5 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt 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 df95109ae..5f8bee341 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 @@ -3,7 +3,6 @@ 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.RectangleShape @@ -16,20 +15,16 @@ import dev.chrisbanes.haze.HazeTint import dev.chrisbanes.haze.hazeEffect /** - * 사진 컴포넌트에 적용되는 그라데이션 배경 - * 좌하단에서 우상단으로 갈수록 어두워지는 효과 + * 사진, 포즈 컴포넌트에 적용되는 그라데이션 배경 */ -fun Modifier.photoBackground( - shape: Shape = RoundedCornerShape(12.dp), +fun Modifier.photoGridBackground( + shape: Shape = RoundedCornerShape(8.dp), ): Modifier = this.background( - brush = Brush.linearGradient( + brush = Brush.verticalGradient( 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), + 0f to Color.Black.copy(alpha = 0.2f), + 134f / 242f to Color.Black.copy(alpha = 0f), ), - start = Offset(0f, Float.POSITIVE_INFINITY), - end = Offset(Float.POSITIVE_INFINITY, 0f), ), shape = shape, ) 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 new file mode 100644 index 000000000..e4ef4e566 --- /dev/null +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/ItemOverlay.kt @@ -0,0 +1,48 @@ +package com.neki.android.core.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp +import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.modifier.photoGridBackground +import com.neki.android.core.designsystem.ui.theme.NekiTheme + +@Composable +fun PhotoGridItemOverlay( + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(8.dp), +) { + Box( + modifier = modifier.photoGridBackground(shape = shape), + ) +} + +@Composable +fun SelectedPhotoGridItemOverlay( + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(8.dp), +) { + Box( + modifier = modifier.background( + color = Color.Black.copy(alpha = 0.2f), + shape = shape, + ), + ) +} + +@ComponentPreview +@Composable +private fun PhotoGridItemOverlayPreview() { + NekiTheme { + PhotoGridItemOverlay( + modifier = Modifier + .size(80.dp), + ) + } +} diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/PhotoComponent.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/PhotoComponent.kt index 2cb84b113..60cd8035d 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/PhotoComponent.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/PhotoComponent.kt @@ -1,7 +1,6 @@ package com.neki.android.core.ui.component import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight @@ -22,7 +21,6 @@ fun PhotoComponent( photo: Photo, modifier: Modifier = Modifier, onClickItem: (Photo) -> Unit = {}, - additionalContent: @Composable BoxScope.() -> Unit = {}, ) { Box( modifier = modifier @@ -37,8 +35,6 @@ fun PhotoComponent( contentDescription = null, contentScale = ContentScale.FillWidth, ) - - additionalContent() } } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt index efc06c283..75d1332a7 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt @@ -2,6 +2,7 @@ package com.neki.android.feature.archive.impl.component import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape @@ -17,48 +18,52 @@ 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.noRippleClickable -import com.neki.android.core.designsystem.modifier.photoBackground 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.SelectedPhotoGridItemOverlay import com.neki.android.core.ui.component.SelectionCheckbox @Composable internal fun SelectablePhotoItem( photo: Photo, isSelected: Boolean, - isSelectMode: Boolean = false, modifier: Modifier = Modifier, + isSelectMode: Boolean = false, onClickItem: (Photo) -> Unit = {}, onClickSelect: (Photo) -> Unit = {}, - onClickFavorite: (Photo) -> Unit = {}, ) { - PhotoComponent( - photo = photo, - modifier = modifier.then( - if (isSelected) Modifier - .border( - width = 2.dp, - color = NekiTheme.colorScheme.primary400, - shape = RoundedCornerShape(12.dp), - ) - .background( - color = Color.Black.copy(alpha = 0.2f), - shape = RoundedCornerShape(12.dp), - ) else Modifier - .clip(RoundedCornerShape(12.dp)) - .photoBackground(), - ), - onClickItem = onClickItem, + Box( + modifier = modifier, ) { - if (isSelectMode) { - SelectionCheckbox( - isSelected = isSelected, - modifier = Modifier - .align(Alignment.TopStart) - .padding(top = 12.dp, start = 12.dp) - .noRippleClickable { onClickSelect(photo) }, - unselectedColor = NekiTheme.colorScheme.white.copy(alpha = 0.2f), + PhotoComponent( + photo = photo, + modifier = Modifier.then( + if (isSelected) Modifier + .border( + width = 2.dp, + color = NekiTheme.colorScheme.primary400, + shape = RoundedCornerShape(12.dp), + ) + .background( + color = Color.Black.copy(alpha = 0.2f), + shape = RoundedCornerShape(12.dp), + ) else Modifier + .clip(RoundedCornerShape(12.dp)), + ), + onClickItem = onClickItem, + ) + + if (isSelected) { + SelectedPhotoGridItemOverlay( + modifier = Modifier.matchParentSize(), + shape = RoundedCornerShape(8.dp), + ) + } else { + PhotoGridItemOverlay( + modifier = Modifier.matchParentSize(), + shape = RoundedCornerShape(8.dp), ) } @@ -67,13 +72,23 @@ internal fun SelectablePhotoItem( modifier = Modifier .align(Alignment.TopEnd) .padding(top = 10.dp, end = 10.dp) - .size(20.dp) - .noRippleClickable { onClickFavorite(photo) }, + .size(20.dp), imageVector = ImageVector.vectorResource(R.drawable.icon_heart), contentDescription = null, tint = NekiTheme.colorScheme.white, ) } + + if (isSelectMode) { + SelectionCheckbox( + isSelected = isSelected, + modifier = Modifier + .align(Alignment.TopStart) + .padding(top = 12.dp, start = 12.dp) + .noRippleClickable { onClickSelect(photo) }, + unselectedColor = NekiTheme.colorScheme.white.copy(alpha = 0.2f), + ) + } } } 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 e2fe110d8..375b9d3ee 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 @@ -1,7 +1,9 @@ package com.neki.android.feature.archive.impl.main.component +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -11,10 +13,10 @@ 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.photoBackground 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 @Composable internal fun ArchiveMainPhotoItem( @@ -22,11 +24,19 @@ internal fun ArchiveMainPhotoItem( modifier: Modifier = Modifier, onClickItem: (Photo) -> Unit = {}, ) { - PhotoComponent( - photo = photo, - modifier = modifier.photoBackground(), - onClickItem = onClickItem, + Box( + modifier = modifier, ) { + PhotoComponent( + photo = photo, + onClickItem = onClickItem, + ) + + PhotoGridItemOverlay( + modifier = Modifier.matchParentSize(), + shape = RoundedCornerShape(8.dp), + ) + if (photo.isFavorite) { Icon( modifier = Modifier From 0210ea20f499a686c2836006719eb25ae4ae94ae Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:33:47 +0900 Subject: [PATCH 09/24] =?UTF-8?q?[feat]=20#78:=20Empty=20=EB=B7=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/icon_empty_content.xml | 126 +++++++++++++++ .../impl/album_detail/AlbumDetailScreen.kt | 150 ++++++++++-------- .../component/AlbumDetailTopBar.kt | 64 ++++++++ .../component/EmptyContent.kt | 59 ++++--- .../archive/impl/photo/AllPhotoScreen.kt | 67 +++++--- .../photo/component/AllPhotoEmptyContent.kt | 76 +++++++++ 6 files changed, 429 insertions(+), 113 deletions(-) create mode 100644 core/designsystem/src/main/res/drawable/icon_empty_content.xml create mode 100644 feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.kt rename feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/{album_detail => }/component/EmptyContent.kt (50%) create mode 100644 feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt diff --git a/core/designsystem/src/main/res/drawable/icon_empty_content.xml b/core/designsystem/src/main/res/drawable/icon_empty_content.xml new file mode 100644 index 000000000..4206622d3 --- /dev/null +++ b/core/designsystem/src/main/res/drawable/icon_empty_content.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt index 23eb27bbe..b95138364 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt @@ -9,33 +9,36 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState +import androidx.paging.PagingData import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.itemKey -import com.neki.android.core.designsystem.topbar.BackTitleTextButtonTopBar -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.DoubleButtonOptionBottomSheet 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.album_detail.component.EmptyContent +import com.neki.android.feature.archive.impl.album_detail.component.AlbumDetailTopBar 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.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 @@ -45,6 +48,7 @@ import com.neki.android.feature.archive.impl.model.SelectMode import com.neki.android.feature.archive.impl.photo.component.PhotoActionBar import com.neki.android.feature.archive.impl.util.ImageDownloader import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.flowOf @Composable internal fun AlbumDetailRoute( @@ -93,20 +97,72 @@ internal fun AlbumDetailScreen( ) { val lazyState = rememberLazyStaggeredGridState() - val isRefreshing = pagingItems.loadState.refresh is LoadState.Loading - val isEmpty = pagingItems.itemCount == 0 && pagingItems.loadState.refresh is LoadState.NotLoading + val isRefreshing by remember { + derivedStateOf { pagingItems.loadState.refresh is LoadState.Loading } + } + val isEmpty by remember { + derivedStateOf { pagingItems.itemCount == 0 && pagingItems.loadState.refresh is LoadState.NotLoading } + } BackHandler(enabled = true) { onIntent(AlbumDetailIntent.OnBackPressed) } + if (isEmpty) { + EmptyContent( + title = if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title, + onClickBack = { onIntent(AlbumDetailIntent.ClickBackIcon) }, + ) + } else { + AlbumDetailContent( + uiState = uiState, + pagingItems = pagingItems, + lazyState = lazyState, + onIntent = onIntent, + ) + } + + if (isRefreshing || uiState.isLoading) { + LoadingDialog() + } + + if (uiState.isShowDeleteDialog) { + DeletePhotoDialog( + onDismissRequest = { onIntent(AlbumDetailIntent.DismissDeleteDialog) }, + onClickDelete = { onIntent(AlbumDetailIntent.ClickDeleteDialogConfirmButton) }, + onClickCancel = { onIntent(AlbumDetailIntent.ClickDeleteDialogCancelButton) }, + ) + } + + if (uiState.isShowDeleteBottomSheet) { + DoubleButtonOptionBottomSheet( + title = "사진을 삭제하시겠어요?", + options = PhotoDeleteOption.entries.toImmutableList(), + selectedOption = uiState.selectedDeleteOption, + primaryButtonText = "삭제하기", + secondaryButtonText = "취소", + onDismissRequest = { onIntent(AlbumDetailIntent.DismissDeleteBottomSheet) }, + onClickSecondaryButton = { onIntent(AlbumDetailIntent.ClickDeleteBottomSheetCancelButton) }, + onClickPrimaryButton = { onIntent(AlbumDetailIntent.ClickDeleteBottomSheetConfirmButton) }, + onOptionSelect = { onIntent(AlbumDetailIntent.SelectDeleteOption(it)) }, + ) + } +} + +@Composable +fun AlbumDetailContent( + uiState: AlbumDetailState, + pagingItems: LazyPagingItems, + lazyState: LazyStaggeredGridState, + modifier: Modifier = Modifier, + onIntent: (AlbumDetailIntent) -> Unit = {}, +) { Column( - modifier = Modifier + modifier = modifier .fillMaxSize() .background(NekiTheme.colorScheme.white), ) { AlbumDetailTopBar( - hasNoPhoto = isEmpty, title = if (uiState.isFavoriteAlbum) "즐겨찾기" else uiState.title, selectMode = uiState.selectMode, onClickBack = { onIntent(AlbumDetailIntent.ClickBackIcon) }, @@ -173,73 +229,29 @@ internal fun AlbumDetailScreen( onClickDelete = { onIntent(AlbumDetailIntent.ClickDeleteIcon) }, ) } +} - if (isRefreshing || uiState.isLoading) { - LoadingDialog() - } - - if (isEmpty) { - EmptyContent( - isFavorite = uiState.isFavoriteAlbum, +@Preview +@Composable +private fun AlbumDetailScreenPreview() { + val photos = (0..10).map { + Photo( + id = it.toLong(), + imageUrl = "", + isFavorite = false, + date = "2024-04-2$it", ) } - if (uiState.isShowDeleteDialog) { - DeletePhotoDialog( - onDismissRequest = { onIntent(AlbumDetailIntent.DismissDeleteDialog) }, - onClickDelete = { onIntent(AlbumDetailIntent.ClickDeleteDialogConfirmButton) }, - onClickCancel = { onIntent(AlbumDetailIntent.ClickDeleteDialogCancelButton) }, - ) - } + val pagingData = PagingData.from(photos) + val pagingItems = flowOf(pagingData).collectAsLazyPagingItems() - if (uiState.isShowDeleteBottomSheet) { - DoubleButtonOptionBottomSheet( - title = "사진을 삭제하시겠어요?", - options = PhotoDeleteOption.entries.toImmutableList(), - selectedOption = uiState.selectedDeleteOption, - primaryButtonText = "삭제하기", - secondaryButtonText = "취소", - onDismissRequest = { onIntent(AlbumDetailIntent.DismissDeleteBottomSheet) }, - onClickSecondaryButton = { onIntent(AlbumDetailIntent.ClickDeleteBottomSheetCancelButton) }, - onClickPrimaryButton = { onIntent(AlbumDetailIntent.ClickDeleteBottomSheetConfirmButton) }, - onOptionSelect = { onIntent(AlbumDetailIntent.SelectDeleteOption(it)) }, - ) - } -} - -@Composable -private fun AlbumDetailTopBar( - title: String, - selectMode: SelectMode, - onClickBack: () -> Unit, - onClickSelect: () -> Unit, - onClickCancel: () -> Unit, - modifier: Modifier = Modifier, - hasNoPhoto: Boolean = false, -) { - if (hasNoPhoto) { - BackTitleTopBar( - modifier = modifier, - title = title, - onBack = onClickBack, - ) - } else { - BackTitleTextButtonTopBar( - modifier = modifier, - title = title, - buttonLabel = when (selectMode) { - SelectMode.DEFAULT -> "선택" - SelectMode.SELECTING -> "취소" - }, - enabledTextColor = when (selectMode) { - SelectMode.DEFAULT -> NekiTheme.colorScheme.primary500 - SelectMode.SELECTING -> NekiTheme.colorScheme.gray800 - }, - onBack = onClickBack, - onClickTextButton = when (selectMode) { - SelectMode.DEFAULT -> onClickSelect - SelectMode.SELECTING -> onClickCancel - }, + NekiTheme { + AlbumDetailScreen( + uiState = AlbumDetailState( + title = "앨범 상세", + ), + pagingItems = pagingItems, ) } } diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.kt new file mode 100644 index 000000000..193ee33e2 --- /dev/null +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.kt @@ -0,0 +1,64 @@ +package com.neki.android.feature.archive.impl.album_detail.component + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.neki.android.core.designsystem.ComponentPreview +import com.neki.android.core.designsystem.topbar.BackTitleTextButtonTopBar +import com.neki.android.core.designsystem.ui.theme.NekiTheme +import com.neki.android.feature.archive.impl.model.SelectMode + +@Composable +internal fun AlbumDetailTopBar( + title: String, + selectMode: SelectMode, + onClickBack: () -> Unit, + onClickSelect: () -> Unit, + onClickCancel: () -> Unit, + modifier: Modifier = Modifier, +) { + BackTitleTextButtonTopBar( + modifier = modifier, + title = title, + buttonLabel = when (selectMode) { + SelectMode.DEFAULT -> "선택" + SelectMode.SELECTING -> "취소" + }, + enabledTextColor = when (selectMode) { + SelectMode.DEFAULT -> NekiTheme.colorScheme.primary500 + SelectMode.SELECTING -> NekiTheme.colorScheme.gray800 + }, + onBack = onClickBack, + onClickTextButton = when (selectMode) { + SelectMode.DEFAULT -> onClickSelect + SelectMode.SELECTING -> onClickCancel + }, + ) +} + +@ComponentPreview +@Composable +private fun AlbumDetailTopBarPreview() { + NekiTheme { + AlbumDetailTopBar( + title = "Album Title", + selectMode = SelectMode.DEFAULT, + onClickBack = {}, + onClickSelect = {}, + onClickCancel = {}, + ) + } +} + +@ComponentPreview +@Composable +private fun AlbumDetailTopBarSelectingPreview() { + NekiTheme { + AlbumDetailTopBar( + title = "Album Title", + selectMode = SelectMode.SELECTING, + onClickBack = {}, + onClickSelect = {}, + onClickCancel = {}, + ) + } +} diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/EmptyContent.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyContent.kt similarity index 50% rename from feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/EmptyContent.kt rename to feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyContent.kt index b7709dec4..402073e14 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/EmptyContent.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/EmptyContent.kt @@ -1,47 +1,53 @@ -package com.neki.android.feature.archive.impl.album_detail.component +package com.neki.android.feature.archive.impl.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.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape +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.draw.clip +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( - isFavorite: Boolean, + 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(22.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { - Box( - modifier = Modifier - .size(104.dp) - .clip(CircleShape) - .background( - color = NekiTheme.colorScheme.gray50, - shape = CircleShape, - ), + Icon( + imageVector = ImageVector.vectorResource(R.drawable.icon_empty_content), + contentDescription = null, + tint = Color.Unspecified, ) Text( - text = if (isFavorite) "아직 등록된 사진이 없어요\n아카이빙 페이지에서 추가해보세요!" - else "아직 등록된 사진이 없어요\n아카이빙 페이지에서 추가해보세요!", + text = emptyText, style = NekiTheme.typography.body14Medium, color = NekiTheme.colorScheme.gray300, textAlign = TextAlign.Center, @@ -50,14 +56,17 @@ internal fun EmptyContent( } } -@ComponentPreview @Composable -private fun FavoriteEmptyContentPreview() { - NekiTheme { - EmptyContent( - isFavorite = true, - ) - } +private fun EmptyTopBar( + title: String, + onClickBack: () -> Unit, + modifier: Modifier = Modifier, +) { + BackTitleTopBar( + modifier = modifier, + title = title, + onBack = onClickBack, + ) } @ComponentPreview @@ -65,7 +74,7 @@ private fun FavoriteEmptyContentPreview() { private fun EmptyContentPreview() { NekiTheme { EmptyContent( - isFavorite = false, + title = "즐겨찾기", ) } } 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 f56d9dfb8..4282a8bd0 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,6 +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.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 @@ -109,6 +110,52 @@ internal fun AllPhotoScreen( pagingItems: LazyPagingItems, lazyState: LazyStaggeredGridState = rememberLazyStaggeredGridState(), onIntent: (AllPhotoIntent) -> Unit = {}, +) { + val isRefreshing by remember(pagingItems) { + derivedStateOf { pagingItems.loadState.refresh is LoadState.Loading } + } + val isEmpty by remember(pagingItems) { + derivedStateOf { pagingItems.itemCount == 0 && pagingItems.loadState.refresh is LoadState.NotLoading } + } + + BackHandler(enabled = true) { + onIntent(AllPhotoIntent.OnBackPressed) + } + + if (isEmpty) { + EmptyContent( + title = "모든 사진", + onClickBack = { onIntent(AllPhotoIntent.ClickTopBarBackIcon) }, + ) + } else { + AllPhotoContent( + uiState = uiState, + pagingItems = pagingItems, + lazyState = lazyState, + onIntent = onIntent, + ) + } + + if (isRefreshing || uiState.isLoading) { + LoadingDialog() + } + + if (uiState.isShowDeleteDialog) { + DeletePhotoDialog( + onDismissRequest = { onIntent(AllPhotoIntent.DismissDeleteDialog) }, + onClickDelete = { onIntent(AllPhotoIntent.ClickDeleteDialogConfirmButton) }, + onClickCancel = { onIntent(AllPhotoIntent.DismissDeleteDialog) }, + ) + } +} + +@Composable +private fun AllPhotoContent( + uiState: AllPhotoState, + pagingItems: LazyPagingItems, + lazyState: LazyStaggeredGridState, + modifier: Modifier = Modifier, + onIntent: (AllPhotoIntent) -> Unit = {}, ) { val density = LocalDensity.current var filterBarHeightPx by remember { mutableIntStateOf(0) } @@ -121,14 +168,8 @@ internal fun AllPhotoScreen( } } - val isRefreshing by remember { derivedStateOf { pagingItems.loadState.refresh is LoadState.Loading } } - - BackHandler(enabled = true) { - onIntent(AllPhotoIntent.OnBackPressed) - } - Column( - modifier = Modifier + modifier = modifier .fillMaxSize() .background(NekiTheme.colorScheme.white), ) { @@ -214,18 +255,6 @@ internal fun AllPhotoScreen( onClickDelete = { onIntent(AllPhotoIntent.ClickDeleteIcon) }, ) } - - if (isRefreshing || uiState.isLoading) { - LoadingDialog() - } - - if (uiState.isShowDeleteDialog) { - DeletePhotoDialog( - onDismissRequest = { onIntent(AllPhotoIntent.DismissDeleteDialog) }, - onClickDelete = { onIntent(AllPhotoIntent.ClickDeleteDialogConfirmButton) }, - onClickCancel = { onIntent(AllPhotoIntent.DismissDeleteDialog) }, - ) - } } @DevicePreview diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt new file mode 100644 index 000000000..5bcad94c1 --- /dev/null +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt @@ -0,0 +1,76 @@ +package com.neki.android.feature.archive.impl.photo.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.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.ui.theme.NekiTheme +import com.neki.android.feature.archive.impl.photo.AllPhotoIntent +import com.neki.android.feature.archive.impl.photo.AllPhotoState + +@Composable +internal fun AllPhotoEmptyContent( + uiState: AllPhotoState, + modifier: Modifier = Modifier, + onIntent: (AllPhotoIntent) -> Unit = {}, +) { + Column( + modifier = modifier + .fillMaxSize() + .background(NekiTheme.colorScheme.white), + ) { + AllPhotoTopBar( + selectMode = uiState.selectMode, + onClickBack = { onIntent(AllPhotoIntent.ClickTopBarBackIcon) }, + onClickSelect = { }, + onClickCancel = { }, + ) + + Box( + modifier = Modifier + .fillMaxSize() + .weight(1f), + contentAlignment = Alignment.Center, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.icon_empty_content), + contentDescription = null, + tint = Color.Unspecified, + ) + Text( + text = "아직 등록된 사진이 없어요\n새로운 사진을 등록하고 앨범에 추가해보세요!", + style = NekiTheme.typography.body14Medium, + color = NekiTheme.colorScheme.gray300, + textAlign = TextAlign.Center, + ) + } + } + } +} + +@ComponentPreview +@Composable +private fun AllPhotoEmptyContentPreview() { + NekiTheme { + AllPhotoEmptyContent( + uiState = AllPhotoState(), + ) + } +} From 019bfef399eb97d97fc2d7fba92573997c9a0205 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:53:43 +0900 Subject: [PATCH 10/24] =?UTF-8?q?[refactor]=20#78:=20=ED=8F=AC=ED=86=A0=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EB=B0=B0=EA=B2=BD=EC=97=90=20alp?= =?UTF-8?q?ha=200.04f=EC=9D=98=20=EA=B2=80=EC=9D=80=EC=83=89=20=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 포토 그리드 아이템의 배경에 `Color.Black.copy(alpha = 0.04f)` 속성의 배경을 추가하여, 기존 그래디언트 배경 위에 새로운 레이어를 더했습니다. --- .../core/designsystem/modifier/Background.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 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 5f8bee341..94ee51e09 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 @@ -19,15 +19,20 @@ import dev.chrisbanes.haze.hazeEffect */ fun Modifier.photoGridBackground( shape: Shape = RoundedCornerShape(8.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), +): Modifier = this + .background( + color = Color.Black.copy(alpha = 0.04f), + shape = shape, + ) + .background( + brush = Brush.verticalGradient( + colorStops = arrayOf( + 0f to Color.Black.copy(alpha = 0.2f), + 134f / 242f to Color.Black.copy(alpha = 0f), + ), ), - ), - shape = shape, -) + shape = shape, + ) /** * 블러 효과가 적용된 배경을 설정하는 Modifier 확장 함수 From 17a0e2eed23d1bc8f07c3e2e53ca70db930ed16a Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:39:34 +0900 Subject: [PATCH 11/24] =?UTF-8?q?[fix]=20#78:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=8B=9C=20EXIF=20?= =?UTF-8?q?=ED=9A=8C=EC=A0=84=20=EB=B0=A9=ED=96=A5=20=EB=B3=B4=EC=A0=95=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 --- core/common/build.gradle.kts | 1 + .../android/core/common/util/ByteArray.kt | 41 ++++++++++++++++++- gradle/libs.versions.toml | 2 + 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index b3377e80e..18926ea0f 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -12,5 +12,6 @@ dependencies { api(libs.timber) implementation(libs.androidx.security.crypto) implementation(libs.androidx.core.ktx) + implementation(libs.androidx.exifinterface) } diff --git a/core/common/src/main/java/com/neki/android/core/common/util/ByteArray.kt b/core/common/src/main/java/com/neki/android/core/common/util/ByteArray.kt index 3b3610dee..d88bf2feb 100644 --- a/core/common/src/main/java/com/neki/android/core/common/util/ByteArray.kt +++ b/core/common/src/main/java/com/neki/android/core/common/util/ByteArray.kt @@ -3,7 +3,9 @@ package com.neki.android.core.common.util import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Matrix import android.net.Uri +import androidx.exifinterface.media.ExifInterface import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream @@ -16,8 +18,22 @@ fun Uri.toByteArray( quality: Int = DEFAULT_QUALITY, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, ): ByteArray? { + val orientation = context.contentResolver.openInputStream(this)?.use { input -> + ExifInterface(input).getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_UNDEFINED, + ) + } ?: ExifInterface.ORIENTATION_UNDEFINED val bytes = context.contentResolver.openInputStream(this)?.use { it.readBytes() } ?: return null - return bytes.compress(quality, format) + val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return null + val rotatedBitmap = bitmap.applyOrientation(orientation) + + return ByteArrayOutputStream().use { outputStream -> + rotatedBitmap.compress(format, quality, outputStream) + if (rotatedBitmap !== bitmap) bitmap.recycle() + rotatedBitmap.recycle() + outputStream.toByteArray() + } } suspend fun String.urlToByteArray( @@ -28,6 +44,29 @@ suspend fun String.urlToByteArray( bytes.compress(quality, format) } +private fun Bitmap.applyOrientation(orientation: Int): Bitmap { + val matrix = Matrix() + when (orientation) { + ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f) + ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f) + ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f) + ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1f, 1f) + ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1f, -1f) + ExifInterface.ORIENTATION_TRANSPOSE -> { + matrix.postRotate(90f) + matrix.postScale(-1f, 1f) + } + + ExifInterface.ORIENTATION_TRANSVERSE -> { + matrix.postRotate(270f) + matrix.postScale(-1f, 1f) + } + + else -> return this + } + return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true) +} + private fun ByteArray.compress( quality: Int, format: Bitmap.CompressFormat, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1ef1cf618..6d1b8bd00 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,7 @@ kakao = "2.23.1" coil = "3.3.0" haze = "1.7.1" lottie = "6.7.1" +exifinterface = "1.4.2" [libraries] androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraX" } @@ -115,6 +116,7 @@ kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-p android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } compose-compiler-gradle-plugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } +androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version.ref = "exifinterface" } [plugins] # Plugins defined by this project From 94e557ec36ca449a0ec4ed1e961c287efa4d88c3 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:39:42 +0900 Subject: [PATCH 12/24] =?UTF-8?q?[refactor]=20#78:=20backgroundHazeBlur=20?= =?UTF-8?q?KDoc=20=EC=A3=BC=EC=84=9D=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=8F=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../neki/android/core/designsystem/modifier/Background.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 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 94ee51e09..8f29c761a 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 @@ -5,7 +5,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -38,10 +37,11 @@ fun Modifier.photoGridBackground( * 블러 효과가 적용된 배경을 설정하는 Modifier 확장 함수 * * @param hazeState Haze 블러 효과를 관리하는 상태 객체 - * @param enabled 블러 효과 활성화 여부 (false일 경우 단색 배경 적용) + * @param alpha 블러 틴트에 적용될 색상의 알파 값 * @param color 블러 효과에 적용될 배경 색상 - * @param defaultBackgroundColor 블러 비활성화 시 적용될 기본 배경 색상 * @param blurRadius 블러 효과의 반경 + * @param enabled 블러 효과 활성화 여부 (false일 경우 단색 배경 적용) + * @param defaultBackgroundColor 블러 비활성화 시 적용될 기본 배경 색상 */ fun Modifier.backgroundHazeBlur( hazeState: HazeState, @@ -50,7 +50,6 @@ fun Modifier.backgroundHazeBlur( blurRadius: Dp, enabled: Boolean = true, defaultBackgroundColor: Color = color, - shape: Shape = RectangleShape, ): Modifier = if (enabled) { this.hazeEffect( @@ -63,5 +62,4 @@ fun Modifier.backgroundHazeBlur( ) } else this.background( color = defaultBackgroundColor, - shape = shape, ) From f18f6d790fbfc9b0fa9d945e2e5cfbb05f97ad4a Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:39:52 +0900 Subject: [PATCH 13/24] =?UTF-8?q?[feat]=20#78:=20ToolTipPopup=20onDismissR?= =?UTF-8?q?equest=20=EC=BD=9C=EB=B0=B1=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8C=9D=EC=97=85=20dismiss=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/designsystem/popup/ToolTipPopup.kt | 2 ++ .../archive/impl/main/ArchiveMainContract.kt | 3 ++- .../feature/archive/impl/main/ArchiveMainScreen.kt | 3 ++- .../archive/impl/main/ArchiveMainViewModel.kt | 3 ++- .../impl/main/component/ArchiveMainTopBar.kt | 14 ++++++++++---- 5 files changed, 18 insertions(+), 7 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 ffb0845c4..1ea95a9bd 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,10 +28,12 @@ fun ToolTipPopup( color: Color, offset: IntOffset, alignment: Alignment, + onDismissRequest: () -> Unit, ) { Popup( alignment = alignment, offset = offset, + onDismissRequest = onDismissRequest, ) { ToolTipContent( tooltipText = tooltipText, diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainContract.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainContract.kt index beac2bf90..dd9debe4d 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainContract.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainContract.kt @@ -32,7 +32,8 @@ sealed interface ArchiveMainIntent { // TopBar Intent data object ClickAddIcon : ArchiveMainIntent - data object DismissAddDialog : ArchiveMainIntent + data object DismissAddPopup : ArchiveMainIntent + data object DismissToolTipPopup : ArchiveMainIntent data object ClickQRScanRow : ArchiveMainIntent data object ClickGalleryUploadRow : ArchiveMainIntent 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 3c531d100..392c89161 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 @@ -119,7 +119,8 @@ internal fun ArchiveMainScreen( onClickQRScan = { onIntent(ArchiveMainIntent.ClickQRScanRow) }, onClickGallery = { onIntent(ArchiveMainIntent.ClickGalleryUploadRow) }, onClickNewAlbum = { onIntent(ArchiveMainIntent.ClickAddNewAlbumRow) }, - onDismissPopup = { onIntent(ArchiveMainIntent.DismissAddDialog) }, + onDismissAddPopup = { onIntent(ArchiveMainIntent.DismissAddPopup) }, + onDismissToolTipPopup = { onIntent(ArchiveMainIntent.DismissToolTipPopup) }, ) ArchiveMainContent( uiState = uiState, diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt index e148ac045..48d58e1d2 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/ArchiveMainViewModel.kt @@ -60,7 +60,8 @@ class ArchiveMainViewModel @Inject constructor( // TopBar Intent ArchiveMainIntent.ClickAddIcon -> reduce { copy(isShowAddDialog = true) } - ArchiveMainIntent.DismissAddDialog -> reduce { copy(isShowAddDialog = false) } + ArchiveMainIntent.DismissAddPopup -> reduce { copy(isShowAddDialog = false) } + ArchiveMainIntent.DismissToolTipPopup -> reduce { copy(isFirstEntered = false) } ArchiveMainIntent.ClickQRScanRow -> { reduce { copy(isShowAddDialog = false) } postSideEffect(ArchiveMainSideEffect.NavigateToQRScan) 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 0e513b807..7b1629d81 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 @@ -42,7 +42,8 @@ internal fun ArchiveMainTopBar( onClickQRScan: () -> Unit = {}, onClickGallery: () -> Unit = {}, onClickNewAlbum: () -> Unit = {}, - onDismissPopup: () -> Unit = {}, + onDismissAddPopup: () -> Unit = {}, + onDismissToolTipPopup: () -> Unit = {}, showTooltip: Boolean = true, ) { NekiLeftTitleTopBar( @@ -67,14 +68,16 @@ internal fun ArchiveMainTopBar( if (showAddPopup) { AddPhotoPopup( - onDismissRequest = onDismissPopup, + onDismissRequest = onDismissAddPopup, onClickQRScan = onClickQRScan, onClickGallery = onClickGallery, onClickNewAlbum = onClickNewAlbum, ) } if (showTooltip) { - ArchiveToolTip() + ArchiveToolTip( + onDismissRequest = onDismissToolTipPopup, + ) } } NekiIconButton( @@ -92,7 +95,9 @@ internal fun ArchiveMainTopBar( } @Composable -private fun ArchiveToolTip() { +private fun ArchiveToolTip( + onDismissRequest: () -> Unit = {}, +) { val density = LocalDensity.current val popupOffsetX = with(density) { 1.dp.toPx().toInt() } val popupOffsetY = with(density) { 47.dp.toPx().toInt() } @@ -103,6 +108,7 @@ private fun ArchiveToolTip() { color = NekiTheme.colorScheme.gray800, offset = offset, alignment = Alignment.TopEnd, + onDismissRequest = onDismissRequest, ) } From 1eaf057edca14b7c889b323e852c0a1b34ca720e Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:43:57 +0900 Subject: [PATCH 14/24] =?UTF-8?q?[fix]=20#78:=20PhotoComponent=20cornerRad?= =?UTF-8?q?ius=2012dp=EC=97=90=EC=84=9C=208dp=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/ui/component/PhotoComponent.kt | 2 +- .../photo/component/AllPhotoEmptyContent.kt | 76 ------------------- 2 files changed, 1 insertion(+), 77 deletions(-) delete mode 100644 feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt diff --git a/core/ui/src/main/java/com/neki/android/core/ui/component/PhotoComponent.kt b/core/ui/src/main/java/com/neki/android/core/ui/component/PhotoComponent.kt index 60cd8035d..da60ed37f 100644 --- a/core/ui/src/main/java/com/neki/android/core/ui/component/PhotoComponent.kt +++ b/core/ui/src/main/java/com/neki/android/core/ui/component/PhotoComponent.kt @@ -24,7 +24,7 @@ fun PhotoComponent( ) { Box( modifier = modifier - .clip(RoundedCornerShape(12.dp)) + .clip(RoundedCornerShape(8.dp)) .noRippleClickable { onClickItem(photo) }, ) { AsyncImage( diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt deleted file mode 100644 index 5bcad94c1..000000000 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/photo/component/AllPhotoEmptyContent.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.neki.android.feature.archive.impl.photo.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.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.ui.theme.NekiTheme -import com.neki.android.feature.archive.impl.photo.AllPhotoIntent -import com.neki.android.feature.archive.impl.photo.AllPhotoState - -@Composable -internal fun AllPhotoEmptyContent( - uiState: AllPhotoState, - modifier: Modifier = Modifier, - onIntent: (AllPhotoIntent) -> Unit = {}, -) { - Column( - modifier = modifier - .fillMaxSize() - .background(NekiTheme.colorScheme.white), - ) { - AllPhotoTopBar( - selectMode = uiState.selectMode, - onClickBack = { onIntent(AllPhotoIntent.ClickTopBarBackIcon) }, - onClickSelect = { }, - onClickCancel = { }, - ) - - Box( - modifier = Modifier - .fillMaxSize() - .weight(1f), - contentAlignment = Alignment.Center, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.icon_empty_content), - contentDescription = null, - tint = Color.Unspecified, - ) - Text( - text = "아직 등록된 사진이 없어요\n새로운 사진을 등록하고 앨범에 추가해보세요!", - style = NekiTheme.typography.body14Medium, - color = NekiTheme.colorScheme.gray300, - textAlign = TextAlign.Center, - ) - } - } - } -} - -@ComponentPreview -@Composable -private fun AllPhotoEmptyContentPreview() { - NekiTheme { - AllPhotoEmptyContent( - uiState = AllPhotoState(), - ) - } -} From ce04f3027375d50743269632a7f0e309438f09d4 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:44:07 +0900 Subject: [PATCH 15/24] =?UTF-8?q?[refactor]=20#78:=20SelectablePhotoItem?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=20=EA=B2=80=EC=9D=80=20?= =?UTF-8?q?=EB=B0=B0=EA=B2=BD=20=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?import=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../archive/impl/component/SelcetablePhotoItem.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt index 75d1332a7..d985a4041 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt @@ -1,6 +1,5 @@ package com.neki.android.feature.archive.impl.component -import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding @@ -11,7 +10,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.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp @@ -40,17 +38,13 @@ internal fun SelectablePhotoItem( PhotoComponent( photo = photo, modifier = Modifier.then( - if (isSelected) Modifier - .border( + if (isSelected) + Modifier.border( width = 2.dp, color = NekiTheme.colorScheme.primary400, shape = RoundedCornerShape(12.dp), - ) - .background( - color = Color.Black.copy(alpha = 0.2f), - shape = RoundedCornerShape(12.dp), - ) else Modifier - .clip(RoundedCornerShape(12.dp)), + ) else + Modifier.clip(RoundedCornerShape(12.dp)), ), onClickItem = onClickItem, ) From aa10101415516fddb530d099cf352f5ea63fea3d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:44:15 +0900 Subject: [PATCH 16/24] =?UTF-8?q?[refactor]=20#78:=20AlbumDetailContent=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=EC=A0=9C=EC=96=B4=EC=9E=90=20internal?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/archive/impl/album_detail/AlbumDetailScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt index b95138364..7021384ba 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/AlbumDetailScreen.kt @@ -150,7 +150,7 @@ internal fun AlbumDetailScreen( } @Composable -fun AlbumDetailContent( +internal fun AlbumDetailContent( uiState: AlbumDetailState, pagingItems: LazyPagingItems, lazyState: LazyStaggeredGridState, From d71f0d11aab422c583c5466955641e20a446cec3 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:52:09 +0900 Subject: [PATCH 17/24] =?UTF-8?q?[refactor]=20#78:=20=EC=95=84=EC=B9=B4?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=20=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=84=B0=EC=B9=98=20=EC=98=81=EC=97=AD=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `offset`으로 구현되어 있던 버튼의 패딩을 `contentPadding`을 사용하도록 변경하여 터치 영역을 확장했습니다. --- .../feature/archive/impl/main/component/ArchiveMainTitleRow.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt index fcf2c6693..e6a2c9834 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt @@ -1,6 +1,7 @@ package com.neki.android.feature.archive.impl.main.component import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset @@ -37,7 +38,7 @@ internal fun ArchiveMainTitleRow( color = NekiTheme.colorScheme.gray900, ) NekiTextButton( - modifier = Modifier.offset(x = ARCHIVE_ROW_TEXT_BUTTON_PADDING.dp), + contentPadding = PaddingValues(vertical = 10.dp), onClick = onClickShowAllAlbum, ) { Row( From b63b96b0acc30313efcfec42a4bca03ca1434073 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:52:53 +0900 Subject: [PATCH 18/24] =?UTF-8?q?[refactor]=20#78:=20BuildConfig=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `build.gradle.kts` 파일에 정의된 일부 BuildConfig 필드명의 오타를 수정합니다. - `PHOTOISM_IMG_URL_MIME_TYPE` → `PHOTOISM_IMAGE_URL_MIME_TYPE` - `LIFE_FOUR_CUT_URL_MIME_TYPE` → `LIFE_FOUR_CUT_IMAGE_URL_MIME_TYPE` 수정된 필드명을 참조하도록 `PhotoWebViewClient.kt` 파일도 함께 변경했습니다. --- feature/photo-upload/impl/build.gradle.kts | 6 ++++-- .../photo_upload/impl/qrscan/util/PhotoWebViewClient.kt | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/feature/photo-upload/impl/build.gradle.kts b/feature/photo-upload/impl/build.gradle.kts index 9f0dac88c..58c6d266d 100644 --- a/feature/photo-upload/impl/build.gradle.kts +++ b/feature/photo-upload/impl/build.gradle.kts @@ -21,12 +21,14 @@ android { defaultConfig { buildConfigField("String", "BRAND_PROPOSAL_URL", properties["BRAND_PROPOSAL_URL"].toString()) + buildConfigField("String", "PHOTOISM_URL", properties["PHOTOISM_URL"].toString()) buildConfigField("String", "PHOTOISM_IMAGE_URL", properties["PHOTOISM_IMAGE_URL"].toString()) - buildConfigField("String", "PHOTOISM_IMG_URL_MIME_TYPE", properties["PHOTOISM_IMG_URL_MIME_TYPE"].toString()) + buildConfigField("String", "PHOTOISM_IMAGE_URL_MIME_TYPE", properties["PHOTOISM_IMAGE_URL_MIME_TYPE"].toString()) + buildConfigField("String", "LIFE_FOUR_CUT_URL", properties["LIFE_FOUR_CUT_URL"].toString()) buildConfigField("String", "LIFE_FOUR_CUT_IMAGE_URL", properties["LIFE_FOUR_CUT_IMAGE_URL"].toString()) - buildConfigField("String", "LIFE_FOUR_CUT_URL_MIME_TYPE", properties["LIFE_FOUR_CUT_URL_MIME_TYPE"].toString()) + buildConfigField("String", "LIFE_FOUR_CUT_IMAGE_URL_MIME_TYPE", properties["LIFE_FOUR_CUT_IMAGE_URL_MIME_TYPE"].toString()) } } diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/util/PhotoWebViewClient.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/util/PhotoWebViewClient.kt index dbbc554d6..60554d2a4 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/util/PhotoWebViewClient.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/qrscan/util/PhotoWebViewClient.kt @@ -20,13 +20,13 @@ class PhotoWebViewClient( when { // 포토이즘 - url.startsWith(BuildConfig.PHOTOISM_IMAGE_URL) && url.endsWith(BuildConfig.PHOTOISM_IMG_URL_MIME_TYPE) -> { + url.startsWith(BuildConfig.PHOTOISM_IMAGE_URL) && url.endsWith(BuildConfig.PHOTOISM_IMAGE_URL_MIME_TYPE) -> { Timber.d("포토이즘 이미지") onImageUrlDetected(url) } // 인생네컷 - url.startsWith(BuildConfig.LIFE_FOUR_CUT_IMAGE_URL) && url.endsWith(BuildConfig.LIFE_FOUR_CUT_URL_MIME_TYPE) -> { + url.startsWith(BuildConfig.LIFE_FOUR_CUT_IMAGE_URL) && url.endsWith(BuildConfig.LIFE_FOUR_CUT_IMAGE_URL_MIME_TYPE) -> { Timber.d("인생네컷 이미지") onImageUrlDetected(url) } From d1833d2c298fabf768e4874890c8f5aaf4f01162 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 11:31:26 +0900 Subject: [PATCH 19/24] =?UTF-8?q?[refactor]=20#78:=20`AlbumDetailTopBar`?= =?UTF-8?q?=EC=9D=98=20=ED=81=B4=EB=A6=AD=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=EC=97=90=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `AlbumDetailTopBar` 컴포저블의 `onClickBack`, `onClickSelect`, `onClickCancel` 파라미터에 기본으로 빈 람다 `{}`를 할당했습니다. 이를 통해 호출부에서 불필요한 빈 콜백을 매번 전달해야 하는 코드를 간소화했습니다. --- .../impl/album_detail/component/AlbumDetailTopBar.kt | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.kt index 193ee33e2..780b10473 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/album_detail/component/AlbumDetailTopBar.kt @@ -11,10 +11,10 @@ import com.neki.android.feature.archive.impl.model.SelectMode internal fun AlbumDetailTopBar( title: String, selectMode: SelectMode, - onClickBack: () -> Unit, - onClickSelect: () -> Unit, - onClickCancel: () -> Unit, modifier: Modifier = Modifier, + onClickBack: () -> Unit = {}, + onClickSelect: () -> Unit = {}, + onClickCancel: () -> Unit = {}, ) { BackTitleTextButtonTopBar( modifier = modifier, @@ -42,9 +42,6 @@ private fun AlbumDetailTopBarPreview() { AlbumDetailTopBar( title = "Album Title", selectMode = SelectMode.DEFAULT, - onClickBack = {}, - onClickSelect = {}, - onClickCancel = {}, ) } } @@ -56,9 +53,6 @@ private fun AlbumDetailTopBarSelectingPreview() { AlbumDetailTopBar( title = "Album Title", selectMode = SelectMode.SELECTING, - onClickBack = {}, - onClickSelect = {}, - onClickCancel = {}, ) } } From 1fcc6c46ffed85732290b96b0a80fd39ee1d633d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 11:31:49 +0900 Subject: [PATCH 20/24] =?UTF-8?q?[fix]=20#78:=20`SelectablePhotoItem`=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20radius=20=EA=B0=92=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `SelcetablePhotoItem`의 파일명을 `SelectablePhotoItem`으로 수정하고, 선택된 사진 아이템의 테두리 radius 값을 12dp에서 8dp로 변경했습니다. --- .../{SelcetablePhotoItem.kt => SelectablePhotoItem.kt} | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/{SelcetablePhotoItem.kt => SelectablePhotoItem.kt} (96%) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelectablePhotoItem.kt similarity index 96% rename from feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt rename to feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelectablePhotoItem.kt index d985a4041..f5ce6067b 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelcetablePhotoItem.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/component/SelectablePhotoItem.kt @@ -42,9 +42,8 @@ internal fun SelectablePhotoItem( Modifier.border( width = 2.dp, color = NekiTheme.colorScheme.primary400, - shape = RoundedCornerShape(12.dp), - ) else - Modifier.clip(RoundedCornerShape(12.dp)), + shape = RoundedCornerShape(8.dp), + ) else Modifier.clip(RoundedCornerShape(8.dp)), ), onClickItem = onClickItem, ) From 9565c8edd57c8bafa04fb8b500c51c45c143d615 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 11:32:02 +0900 Subject: [PATCH 21/24] =?UTF-8?q?[refactor]=20#78:=20=EC=95=A8=EB=B2=94=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20Arrangement.?= =?UTF-8?q?spacedBy(20.dp)=20=EC=86=8D=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/feature/photo_upload/impl/album/UploadAlbumScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt index d55004bd5..2d84cdf7d 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt @@ -71,7 +71,6 @@ internal fun UploadAlbumScreen( LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(20.dp), ) { item { FavoriteAlbumRowComponent(album = uiState.favoriteAlbum) From b233af27ec134c0d1e381c9906a98ca519773336 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 11:35:50 +0900 Subject: [PATCH 22/24] =?UTF-8?q?[refactor]=20#78:=20ItemOverlay=EC=97=90?= =?UTF-8?q?=EC=84=9C=20photoGridBackground=20Modifier=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `photoGridBackground` Modifier 확장 함수를 제거하고, `ItemOverlay` 컴포저블 내부에서 `Modifier.background`를 직접 사용하도록 수정했습니다. --- .../core/designsystem/modifier/Background.kt | 24 ------------------- .../android/core/ui/component/ItemOverlay.kt | 17 +++++++++++-- 2 files changed, 15 insertions(+), 26 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 8f29c761a..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,38 +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.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.photoGridBackground( - shape: Shape = RoundedCornerShape(8.dp), -): Modifier = this - .background( - color = Color.Black.copy(alpha = 0.04f), - shape = shape, - ) - .background( - brush = Brush.verticalGradient( - colorStops = arrayOf( - 0f to Color.Black.copy(alpha = 0.2f), - 134f / 242f to Color.Black.copy(alpha = 0f), - ), - ), - shape = shape, - ) - /** * 블러 효과가 적용된 배경을 설정하는 Modifier 확장 함수 * 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 e4ef4e566..661f1104e 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 @@ -6,11 +6,11 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +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 com.neki.android.core.designsystem.ComponentPreview -import com.neki.android.core.designsystem.modifier.photoGridBackground import com.neki.android.core.designsystem.ui.theme.NekiTheme @Composable @@ -19,7 +19,20 @@ fun PhotoGridItemOverlay( shape: Shape = RoundedCornerShape(8.dp), ) { Box( - modifier = modifier.photoGridBackground(shape = shape), + modifier = modifier + .background( + color = Color.Black.copy(alpha = 0.04f), + shape = shape, + ) + .background( + brush = Brush.verticalGradient( + colorStops = arrayOf( + 0f to Color.Black.copy(alpha = 0.2f), + 134f / 242f to Color.Black.copy(alpha = 0f), + ), + ), + shape = shape, + ), ) } From 7545aa4747d1d411239b938ce85ba0f540e6074a Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 11:37:26 +0900 Subject: [PATCH 23/24] =?UTF-8?q?[chore]=20#78:=20detekt=20=EB=A6=B0?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/archive/impl/main/component/ArchiveMainTitleRow.kt | 2 -- .../feature/photo_upload/impl/album/UploadAlbumScreen.kt | 1 - 2 files changed, 3 deletions(-) diff --git a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt index e6a2c9834..e3a790323 100644 --- a/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt +++ b/feature/archive/impl/src/main/kotlin/com/neki/android/feature/archive/impl/main/component/ArchiveMainTitleRow.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -18,7 +17,6 @@ import com.neki.android.core.designsystem.ComponentPreview import com.neki.android.core.designsystem.R import com.neki.android.core.designsystem.button.NekiTextButton import com.neki.android.core.designsystem.ui.theme.NekiTheme -import com.neki.android.feature.archive.impl.const.ArchiveConst.ARCHIVE_ROW_TEXT_BUTTON_PADDING @Composable internal fun ArchiveMainTitleRow( diff --git a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt index 2d84cdf7d..8153999c0 100644 --- a/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt +++ b/feature/photo-upload/impl/src/main/java/com/neki/android/feature/photo_upload/impl/album/UploadAlbumScreen.kt @@ -1,7 +1,6 @@ package com.neki.android.feature.photo_upload.impl.album import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize From f9f49af7a9146b6f69eb76bbe0ad8c339395078d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Sat, 7 Feb 2026 13:29:26 +0900 Subject: [PATCH 24/24] =?UTF-8?q?[fix]=20#78:=20=EB=9E=9C=EB=8D=A4=20?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=9D=90?= =?UTF-8?q?=EB=A6=BC=20=ED=9A=A8=EA=B3=BC=20=EB=B0=8F=20=EA=B7=B8=EB=9D=BC?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=EC=85=98=20=EB=B0=B0=EA=B2=BD=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 포즈피드 랜덤 아이템 UI에 그라데이션 배경과 흐림(blur) 효과를 적용했습니다. --- .../neki/android/core/designsystem/modifier/Background.kt | 5 +++++ 1 file changed, 5 insertions(+) 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 da2baa458..df3370121 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,9 +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