From 3d6e984560157a171e79e06394d200d4c65a3d37 Mon Sep 17 00:00:00 2001 From: s9hn Date: Wed, 4 Jun 2025 23:52:14 +0900 Subject: [PATCH 01/53] =?UTF-8?q?build:=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/library/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/feature/library/build.gradle.kts b/feature/library/build.gradle.kts index f123daecc..3d0808734 100644 --- a/feature/library/build.gradle.kts +++ b/feature/library/build.gradle.kts @@ -10,6 +10,7 @@ android { dependencies { implementation(projects.domain.library) + implementation(projects.data.library) implementation("androidx.paging:paging-runtime:3.3.2") } From 5a4f54dfce29e14ca29a2623fc6e1295bab6501d Mon Sep 17 00:00:00 2001 From: s9hn Date: Thu, 5 Jun 2025 23:58:32 +0900 Subject: [PATCH 02/53] =?UTF-8?q?build:=20=ED=8E=98=EC=9D=B4=EC=A7=95=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20toml=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6bed31336..efaa0a5e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ lifecycle-extensions = "2.2.0" datastore-preferences = "1.1.1" security-crypto = "1.1.0-alpha06" room = "2.6.1" +paging = "3.3.1" # Testing Libraries junit = "4.13.2" From 266b4b552bbf1d477cdb5c142fe6535d68af39ff Mon Sep 17 00:00:00 2001 From: s9hn Date: Sat, 7 Jun 2025 23:47:25 +0900 Subject: [PATCH 03/53] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/into/websoso/feature/library/LibraryScreen.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt index 02e6b5a17..4a9dc5e3f 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt @@ -1,6 +1,5 @@ package com.into.websoso.feature.library -import android.util.Log import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text @@ -13,7 +12,7 @@ import androidx.hilt.navigation.compose.hiltViewModel fun LibraryScreen(libraryViewModel: LibraryViewModel = hiltViewModel()) { LaunchedEffect(Unit) { libraryViewModel.novelList.collect { - Log.d("123123", it.toString()) + // 페이징 } } From 1c6af7e527684f259d11b353139871988ac5a7e3 Mon Sep 17 00:00:00 2001 From: s9hn Date: Wed, 11 Jun 2025 23:58:23 +0900 Subject: [PATCH 04/53] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/into/websoso/data/library/LibraryRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt b/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt index a07bfbb34..1e323526f 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt @@ -15,7 +15,7 @@ class LibraryRepository @Inject constructor( private val libraryRemoteDataSource: LibraryRemoteDataSource, - private val libraryLocalDataSource: LibraryLocalDataSource, + // private val libraryLocalDataSource: LibraryLocalDataSource, ) { fun getUserLibrary( userId: Long, From fedb86d876ff44e73bea43cce52dcef18a3f9e67 Mon Sep 17 00:00:00 2001 From: s9hn Date: Fri, 13 Jun 2025 23:49:37 +0900 Subject: [PATCH 05/53] =?UTF-8?q?feat:=20LibraryRemoteMediator=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/into/websoso/data/library/LibraryRemoteMediator.kt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt diff --git a/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt b/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt new file mode 100644 index 000000000..7496df38f --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt @@ -0,0 +1,5 @@ +package com.into.websoso.data.library + +import com.into.websoso.data.library.model.NovelEntity + +class LibraryRemoteMediator : RemoteMediator() From a397a404b9b89613b2d83658295e9ce93fd918b9 Mon Sep 17 00:00:00 2001 From: s9hn Date: Sat, 14 Jun 2025 23:57:24 +0900 Subject: [PATCH 06/53] =?UTF-8?q?feat:=20LibraryRemoteMediator=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/into/websoso/data/library/LibraryRemoteMediator.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt b/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt index 7496df38f..bef145a2d 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt @@ -1,5 +1,8 @@ package com.into.websoso.data.library import com.into.websoso.data.library.model.NovelEntity +import androidx.paging.RemoteMediator -class LibraryRemoteMediator : RemoteMediator() +class LibraryRemoteMediator : RemoteMediator() { + +} From 0cc47609693f234a63d670fff5f4f12551d03d7e Mon Sep 17 00:00:00 2001 From: s9hn Date: Sun, 15 Jun 2025 23:56:52 +0900 Subject: [PATCH 07/53] =?UTF-8?q?feat:=20LibraryRemoteMediator=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/into/websoso/data/library/LibraryRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt b/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt index 1e323526f..a07bfbb34 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt @@ -15,7 +15,7 @@ class LibraryRepository @Inject constructor( private val libraryRemoteDataSource: LibraryRemoteDataSource, - // private val libraryLocalDataSource: LibraryLocalDataSource, + private val libraryLocalDataSource: LibraryLocalDataSource, ) { fun getUserLibrary( userId: Long, From c830ff1272c90e260d8d1f672511a0dee2842c70 Mon Sep 17 00:00:00 2001 From: s9hn Date: Wed, 2 Jul 2025 17:49:27 +0900 Subject: [PATCH 08/53] =?UTF-8?q?refactor:=20LibraryRemoteMediator=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../into/websoso/data/library/LibraryRemoteMediator.kt | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt diff --git a/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt b/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt deleted file mode 100644 index bef145a2d..000000000 --- a/data/library/src/main/java/com/into/websoso/data/library/LibraryRemoteMediator.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.into.websoso.data.library - -import com.into.websoso.data.library.model.NovelEntity -import androidx.paging.RemoteMediator - -class LibraryRemoteMediator : RemoteMediator() { - -} From fbad1ac5b3d5b4544774690985a12ef552f9c717 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Thu, 3 Jul 2025 17:00:00 +0900 Subject: [PATCH 09/53] =?UTF-8?q?add:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=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 --- .../res/drawable/ic_library_character.xml | 15 ++++++++++++ .../drawable/ic_library_drop_down_fill.xml | 11 +++++++++ .../src/main/res/drawable/ic_library_grid.xml | 18 ++++++++++++++ .../res/drawable/ic_library_half_star.xml | 12 ++++++++++ .../res/drawable/ic_library_interesting.xml | 12 ++++++++++ .../src/main/res/drawable/ic_library_list.xml | 24 +++++++++++++++++++ .../main/res/drawable/ic_library_material.xml | 9 +++++++ .../res/drawable/ic_library_null_star.xml | 9 +++++++ .../drawable/ic_library_quote_finished.xml | 9 +++++++ .../res/drawable/ic_library_quote_started.xml | 9 +++++++ .../res/drawable/ic_library_relationship.xml | 13 ++++++++++ .../src/main/res/drawable/ic_library_sort.xml | 13 ++++++++++ .../src/main/res/drawable/ic_library_vibe.xml | 9 +++++++ .../res/drawable/ic_library_world_view.xml | 9 +++++++ 14 files changed, 172 insertions(+) create mode 100644 core/resource/src/main/res/drawable/ic_library_character.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_drop_down_fill.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_grid.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_half_star.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_interesting.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_list.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_material.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_null_star.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_quote_finished.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_quote_started.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_relationship.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_sort.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_vibe.xml create mode 100644 core/resource/src/main/res/drawable/ic_library_world_view.xml diff --git a/core/resource/src/main/res/drawable/ic_library_character.xml b/core/resource/src/main/res/drawable/ic_library_character.xml new file mode 100644 index 000000000..980f13565 --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_character.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/resource/src/main/res/drawable/ic_library_drop_down_fill.xml b/core/resource/src/main/res/drawable/ic_library_drop_down_fill.xml new file mode 100644 index 000000000..66853cd2b --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_drop_down_fill.xml @@ -0,0 +1,11 @@ + + + diff --git a/core/resource/src/main/res/drawable/ic_library_grid.xml b/core/resource/src/main/res/drawable/ic_library_grid.xml new file mode 100644 index 000000000..9c3d44139 --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_grid.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/core/resource/src/main/res/drawable/ic_library_half_star.xml b/core/resource/src/main/res/drawable/ic_library_half_star.xml new file mode 100644 index 000000000..9bf056789 --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_half_star.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/resource/src/main/res/drawable/ic_library_interesting.xml b/core/resource/src/main/res/drawable/ic_library_interesting.xml new file mode 100644 index 000000000..88a9a70bb --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_interesting.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/resource/src/main/res/drawable/ic_library_list.xml b/core/resource/src/main/res/drawable/ic_library_list.xml new file mode 100644 index 000000000..b026ab2d4 --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_list.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/core/resource/src/main/res/drawable/ic_library_material.xml b/core/resource/src/main/res/drawable/ic_library_material.xml new file mode 100644 index 000000000..0603ff018 --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_material.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/resource/src/main/res/drawable/ic_library_null_star.xml b/core/resource/src/main/res/drawable/ic_library_null_star.xml new file mode 100644 index 000000000..56e69cbce --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_null_star.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/resource/src/main/res/drawable/ic_library_quote_finished.xml b/core/resource/src/main/res/drawable/ic_library_quote_finished.xml new file mode 100644 index 000000000..323547d44 --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_quote_finished.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/resource/src/main/res/drawable/ic_library_quote_started.xml b/core/resource/src/main/res/drawable/ic_library_quote_started.xml new file mode 100644 index 000000000..cd4fa3f40 --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_quote_started.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/resource/src/main/res/drawable/ic_library_relationship.xml b/core/resource/src/main/res/drawable/ic_library_relationship.xml new file mode 100644 index 000000000..6ba5358ea --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_relationship.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core/resource/src/main/res/drawable/ic_library_sort.xml b/core/resource/src/main/res/drawable/ic_library_sort.xml new file mode 100644 index 000000000..b7d041aad --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_sort.xml @@ -0,0 +1,13 @@ + + + diff --git a/core/resource/src/main/res/drawable/ic_library_vibe.xml b/core/resource/src/main/res/drawable/ic_library_vibe.xml new file mode 100644 index 000000000..eb4e26d33 --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_vibe.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/resource/src/main/res/drawable/ic_library_world_view.xml b/core/resource/src/main/res/drawable/ic_library_world_view.xml new file mode 100644 index 000000000..69651e50a --- /dev/null +++ b/core/resource/src/main/res/drawable/ic_library_world_view.xml @@ -0,0 +1,9 @@ + + + From aeb0cbe80694ef6ec726862407e9cbb8453fce06 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Thu, 3 Jul 2025 17:04:50 +0900 Subject: [PATCH 10/53] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20null=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibraryEmptyView.kt | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt new file mode 100644 index 000000000..a6a99cf76 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt @@ -0,0 +1,85 @@ +package com.into.websoso.feature.library.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +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.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +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.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.into.websoso.core.designsystem.theme.Gray200 +import com.into.websoso.core.designsystem.theme.Primary100 +import com.into.websoso.core.designsystem.theme.Primary50 +import com.into.websoso.core.designsystem.theme.WebsosoTheme +import com.into.websoso.core.resource.R.drawable.ic_storage_null +import com.into.websoso.feature.library.R.string.library_empty +import com.into.websoso.feature.library.R.string.library_go_to_explore + +@Composable +fun LibraryEmptyView(onExploreClick: () -> Unit = {}) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(top = 80.dp), + contentAlignment = Alignment.TopCenter, + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Image( + imageVector = ImageVector.vectorResource(id = ic_storage_null), + contentDescription = null, + modifier = Modifier.size(64.dp), + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(id = library_empty), + style = WebsosoTheme.typography.body1, + color = Gray200, + ) + Spacer(modifier = Modifier.height(44.dp)) + LibraryExploreButton( + onClick = onExploreClick, + ) + } + } +} + +@Composable +fun LibraryExploreButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + Button( + onClick = onClick, + modifier = modifier + .fillMaxWidth(0.6f), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Primary50, + ), + contentPadding = PaddingValues( + horizontal = 20.dp, + vertical = 18.dp, + ), + elevation = null, + ) { + Text( + text = stringResource(id = library_go_to_explore), + style = WebsosoTheme.typography.title1, + color = Primary100, + ) + } +} From 9e2cf2fcf68abe6db35df968dda772163711b819 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Thu, 3 Jul 2025 20:46:54 +0900 Subject: [PATCH 11/53] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20=ED=83=91=EB=B0=94=20=EC=BB=B4=ED=8F=AC=EC=A0=80?= =?UTF-8?q?=EB=B8=94=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibrayFilterTopBar.kt | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt new file mode 100644 index 000000000..ab3dd4f8f --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt @@ -0,0 +1,221 @@ +package com.into.websoso.feature.library.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +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.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.into.websoso.core.designsystem.theme.Black +import com.into.websoso.core.designsystem.theme.Gray200 +import com.into.websoso.core.designsystem.theme.Gray300 +import com.into.websoso.core.designsystem.theme.Gray70 +import com.into.websoso.core.designsystem.theme.WebsosoTheme +import com.into.websoso.core.designsystem.theme.White +import com.into.websoso.core.resource.R.drawable.ic_library_drop_down_fill +import com.into.websoso.core.resource.R.drawable.ic_library_grid +import com.into.websoso.core.resource.R.drawable.ic_library_list +import com.into.websoso.core.resource.R.drawable.ic_library_sort +import com.into.websoso.domain.library.model.SortType +import com.into.websoso.feature.library.R.string.library_attractive_point +import com.into.websoso.feature.library.R.string.library_interesting +import com.into.websoso.feature.library.R.string.library_novel_count +import com.into.websoso.feature.library.R.string.library_rating +import com.into.websoso.feature.library.R.string.library_read_status +import com.into.websoso.feature.library.model.FilterType +import com.into.websoso.feature.library.model.LibraryFilterUiState +import com.into.websoso.feature.library.util.buildFilterLabel + +@Composable +fun LibraryFilterTopBar( + libraryFilterUiState: LibraryFilterUiState, + totalCount: Int, + onFilterClick: (FilterType) -> Unit, + selectedSortType: SortType, + onSortClick: () -> Unit, + isGrid: Boolean, + onToggleViewType: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(start = 20.dp), + ) { + NovelFilterChipSection( + libraryFilterUiState = libraryFilterUiState, + onFilterClick = onFilterClick, + ) + + Spacer(modifier = Modifier.height(10.dp)) + + NovelFilterStatusBar( + totalCount = totalCount, + selectedSortType = selectedSortType, + isGrid = isGrid, + onSortClick = onSortClick, + onToggleViewType = onToggleViewType, + ) + } +} + +@Composable +fun NovelFilterChipSection( + libraryFilterUiState: LibraryFilterUiState, + onFilterClick: (FilterType) -> Unit, +) { + Row( + modifier = Modifier + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + NovelFilterChip( + text = stringResource(id = library_interesting), + isSelected = libraryFilterUiState.isInterested, + onClick = { onFilterClick(FilterType.Interest) }, + showDropdownIcon = false, + ) + + Box( + modifier = Modifier + .padding(horizontal = 4.dp) + .width(1.dp) + .height(32.dp) + .background(color = Gray70), + ) + + NovelFilterChip( + text = buildFilterLabel( + stringResource(id = library_read_status), + libraryFilterUiState.readStatusLabel, + ), + isSelected = libraryFilterUiState.readStatusSelected, + onClick = { onFilterClick(FilterType.ReadStatus) }, + ) + + NovelFilterChip( + text = stringResource(id = library_rating), + isSelected = libraryFilterUiState.ratingSelected, + onClick = { onFilterClick(FilterType.Rating) }, + ) + + NovelFilterChip( + text = buildFilterLabel( + stringResource(id = library_attractive_point), + libraryFilterUiState.attractivePointLabel, + ), + isSelected = libraryFilterUiState.attractivePointSelected, + onClick = { onFilterClick(FilterType.AttractivePoint) }, + ) + } +} + +@Composable +fun NovelFilterChip( + text: String, + isSelected: Boolean, + onClick: () -> Unit, + showDropdownIcon: Boolean = true, +) { + val backgroundColor = if (isSelected) Black else White + val textColor = if (isSelected) White else Gray300 + + Surface( + color = backgroundColor, + shape = RoundedCornerShape(20.dp), + modifier = Modifier + .defaultMinSize(minHeight = 32.dp) + .clickable(onClick = onClick), + border = if (!isSelected) BorderStroke(1.dp, Gray70) else null, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = 14.dp, vertical = 6.dp), + ) { + Text( + text = text, + color = textColor, + style = WebsosoTheme.typography.body5, + ) + if (showDropdownIcon) { + Image( + imageVector = ImageVector.vectorResource(id = ic_library_drop_down_fill), + contentDescription = null, + modifier = Modifier + .padding(start = 4.dp) + .size(12.dp), + ) + } + } + } +} + +@Composable +fun NovelFilterStatusBar( + totalCount: Int, + selectedSortType: SortType, + isGrid: Boolean, + onSortClick: () -> Unit, + onToggleViewType: () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(library_novel_count, totalCount), + style = WebsosoTheme.typography.body4, + color = Gray200, + ) + + Row(verticalAlignment = Alignment.CenterVertically) { + TextButton(onClick = onSortClick) { + Image( + imageVector = ImageVector.vectorResource(id = ic_library_sort), + contentDescription = null, + modifier = Modifier.size(16.dp), + ) + + Text( + text = selectedSortType.displayName, + style = WebsosoTheme.typography.body5, + color = Gray300, + ) + } + + IconButton(onClick = onToggleViewType) { + Image( + imageVector = ImageVector.vectorResource( + id = if (isGrid) ic_library_grid else ic_library_list, + ), + contentDescription = "리스트 형태 전환", + modifier = Modifier.size(16.dp), + ) + } + } + } +} From 493b5ad7abc02d2e54896b12c47a03cbaf7ca33b Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Thu, 3 Jul 2025 20:58:18 +0900 Subject: [PATCH 12/53] =?UTF-8?q?refactor:=20=EC=84=9C=EC=9E=AC=ED=95=84?= =?UTF-8?q?=ED=84=B0=ED=83=80=EC=9E=85=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibrayFilterTopBar.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt index ab3dd4f8f..2670d7957 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt @@ -45,7 +45,7 @@ import com.into.websoso.feature.library.R.string.library_interesting import com.into.websoso.feature.library.R.string.library_novel_count import com.into.websoso.feature.library.R.string.library_rating import com.into.websoso.feature.library.R.string.library_read_status -import com.into.websoso.feature.library.model.FilterType +import com.into.websoso.feature.library.model.LibraryFilterType import com.into.websoso.feature.library.model.LibraryFilterUiState import com.into.websoso.feature.library.util.buildFilterLabel @@ -53,7 +53,7 @@ import com.into.websoso.feature.library.util.buildFilterLabel fun LibraryFilterTopBar( libraryFilterUiState: LibraryFilterUiState, totalCount: Int, - onFilterClick: (FilterType) -> Unit, + onFilterClick: (LibraryFilterType) -> Unit, selectedSortType: SortType, onSortClick: () -> Unit, isGrid: Boolean, @@ -85,7 +85,7 @@ fun LibraryFilterTopBar( @Composable fun NovelFilterChipSection( libraryFilterUiState: LibraryFilterUiState, - onFilterClick: (FilterType) -> Unit, + onFilterClick: (LibraryFilterType) -> Unit, ) { Row( modifier = Modifier @@ -95,7 +95,7 @@ fun NovelFilterChipSection( NovelFilterChip( text = stringResource(id = library_interesting), isSelected = libraryFilterUiState.isInterested, - onClick = { onFilterClick(FilterType.Interest) }, + onClick = { onFilterClick(LibraryFilterType.Interest) }, showDropdownIcon = false, ) @@ -113,13 +113,13 @@ fun NovelFilterChipSection( libraryFilterUiState.readStatusLabel, ), isSelected = libraryFilterUiState.readStatusSelected, - onClick = { onFilterClick(FilterType.ReadStatus) }, + onClick = { onFilterClick(LibraryFilterType.ReadStatus) }, ) NovelFilterChip( text = stringResource(id = library_rating), isSelected = libraryFilterUiState.ratingSelected, - onClick = { onFilterClick(FilterType.Rating) }, + onClick = { onFilterClick(LibraryFilterType.Rating) }, ) NovelFilterChip( @@ -128,7 +128,7 @@ fun NovelFilterChipSection( libraryFilterUiState.attractivePointLabel, ), isSelected = libraryFilterUiState.attractivePointSelected, - onClick = { onFilterClick(FilterType.AttractivePoint) }, + onClick = { onFilterClick(LibraryFilterType.AttractivePoint) }, ) } } From a70cc4d191de0c20dcfd35f234e47835a03fe474 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Thu, 3 Jul 2025 21:26:47 +0900 Subject: [PATCH 13/53] =?UTF-8?q?feat:=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=95=84=EC=9D=B4=ED=85=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibraryGridListItem.kt | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt new file mode 100644 index 000000000..a138dcbe1 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt @@ -0,0 +1,206 @@ +package com.into.websoso.feature.library.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.into.websoso.core.designsystem.theme.Black +import com.into.websoso.core.designsystem.theme.Gray200 +import com.into.websoso.core.designsystem.theme.WebsosoTheme +import com.into.websoso.core.designsystem.theme.White +import com.into.websoso.core.resource.R.drawable.ic_library_half_star +import com.into.websoso.core.resource.R.drawable.ic_library_interesting +import com.into.websoso.core.resource.R.drawable.ic_library_null_star +import com.into.websoso.core.resource.R.drawable.ic_storage_star +import com.into.websoso.feature.library.model.LibraryListItemModel +import com.into.websoso.feature.library.model.RatingStarType +import com.into.websoso.feature.library.model.ReadStatus +import com.into.websoso.feature.library.util.formatDateRange + +private const val GRID_COLUMN_COUNT = 3 +private val ITEM_SPACING = 6.dp +private val HORIZONTAL_PADDING = 20.dp +private const val IMAGE_ASPECT_WIDTH = 102.67f +private const val IMAGE_ASPECT_HEIGHT = 160f + +@Composable +fun NovelGridListItem( + item: LibraryListItemModel, + modifier: Modifier = Modifier, + onItemClick: () -> Unit = {}, +) { + val itemSize = rememberGridItemSize() + + Column( + modifier = modifier + .width(itemSize.width) + .wrapContentHeight() + .clickable { onItemClick() }, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + NovelGridThumbnail( + item = item, + size = itemSize, + ) + + Text( + text = item.title, + style = WebsosoTheme.typography.body4, + color = Black, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + + item.myRating?.let { + NovelRatingStar(rating = it) + } + + formatDateRange(item.startDate, item.endDate)?.let { + Text( + text = it, + style = WebsosoTheme.typography.label2, + color = Gray200, + ) + } + } +} + +@Composable +fun NovelGridThumbnail( + item: LibraryListItemModel, + size: GridItemSize, +) { + Box( + modifier = Modifier + .size(width = size.width, height = size.height) + .clip(RoundedCornerShape(8.dp)), + ) { + AsyncImage( + model = item.novelImageUrl, + contentDescription = item.title, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + + item.readStatus?.let { + ReadStatusBadge( + readStatus = it, + modifier = Modifier + .align(Alignment.BottomStart) + .padding(6.dp), + ) + } + + if (item.isInterested) { + Image( + imageVector = ImageVector.vectorResource(id = ic_library_interesting), + contentDescription = null, + modifier = Modifier + .size(30.dp) + .align(Alignment.BottomEnd) + .padding(6.dp), + ) + } + } +} + +@Composable +fun ReadStatusBadge( + readStatus: ReadStatus, + modifier: Modifier = Modifier, +) { + Text( + text = readStatus.label, + color = White, + style = WebsosoTheme.typography.label2, + modifier = modifier + .background( + color = readStatus.backgroundColor, + shape = RoundedCornerShape(4.dp), + ).padding(horizontal = 8.dp, vertical = 4.dp), + ) +} + +@Composable +fun rememberGridItemSize(): GridItemSize { + val screenWidth = LocalConfiguration.current.screenWidthDp + val density = LocalDensity.current + + return remember(screenWidth) { + with(density) { + val totalSpacingPx = (ITEM_SPACING * (GRID_COLUMN_COUNT - 1) + HORIZONTAL_PADDING * 2).toPx() + val itemWidthPx = ((screenWidth.dp.toPx() - totalSpacingPx) / GRID_COLUMN_COUNT) + val itemHeightPx = itemWidthPx * (IMAGE_ASPECT_HEIGHT / IMAGE_ASPECT_WIDTH) + + GridItemSize(itemWidthPx.toDp(), itemHeightPx.toDp()) + } + } +} + +@Composable +fun NovelRatingStar( + rating: Float, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(2.dp), + ) { + calculateRatingStars(rating).forEach { starType -> + Image( + imageVector = ratingStarIcon(starType), + contentDescription = null, + modifier = Modifier.size(14.dp), + ) + } + } +} + +@Composable +private fun ratingStarIcon(starType: RatingStarType): ImageVector = + when (starType) { + RatingStarType.FULL -> ImageVector.vectorResource(id = ic_storage_star) + RatingStarType.HALF -> ImageVector.vectorResource(id = ic_library_half_star) + RatingStarType.EMPTY -> ImageVector.vectorResource(id = ic_library_null_star) + } + +private fun calculateRatingStars(rating: Float): List { + val fullStar = rating.toInt() + val halfStar = (rating - fullStar) >= 0.5f + val emptyStar = 5 - fullStar - if (halfStar) 1 else 0 + + return buildList { + repeat(fullStar) { add(RatingStarType.FULL) } + if (halfStar) add(RatingStarType.HALF) + repeat(emptyStar) { add(RatingStarType.EMPTY) } + } +} + +data class GridItemSize( + val width: Dp, + val height: Dp, +) From 37364fefbb0b25ef12e3a120a5fa2e9552c3ef95 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Thu, 3 Jul 2025 21:26:58 +0900 Subject: [PATCH 14/53] =?UTF-8?q?feat:=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EC=A0=80?= =?UTF-8?q?=EB=B8=94=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibraryGridList.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt new file mode 100644 index 000000000..82463ad89 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt @@ -0,0 +1,37 @@ +package com.into.websoso.feature.library.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.into.websoso.feature.library.model.LibraryListItemModel + +@Composable +fun NovelGridList( + novels: List, + gridState: LazyGridState, + modifier: Modifier = Modifier, + onItemClick: (LibraryListItemModel) -> Unit = {}, +) { + LazyVerticalGrid( + columns = GridCells.Fixed(3), + state = gridState, + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 20.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(18.dp), + ) { + items(novels, key = { it.title }) { novel -> + NovelGridListItem( + item = novel, + onItemClick = { onItemClick(novel) }, + ) + } + } +} From fe5d29593b7578ebd1fccc46320252fc8a1d38e8 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Thu, 3 Jul 2025 21:40:12 +0900 Subject: [PATCH 15/53] =?UTF-8?q?refactor:=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?=EB=85=B8=EC=B6=9C=EC=9D=B4=20=ED=95=84=EC=9A=94=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=95=A8=EC=88=98=EB=93=A4=20private=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibraryEmptyView.kt | 2 +- .../library/component/LibraryGridListItem.kt | 17 +++++++++++------ .../library/component/LibrayFilterTopBar.kt | 6 +++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt index a6a99cf76..6d1e9381b 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt @@ -58,7 +58,7 @@ fun LibraryEmptyView(onExploreClick: () -> Unit = {}) { } @Composable -fun LibraryExploreButton( +private fun LibraryExploreButton( modifier: Modifier = Modifier, onClick: () -> Unit, ) { diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt index a138dcbe1..9ff3b44a3 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt @@ -37,7 +37,6 @@ import com.into.websoso.core.resource.R.drawable.ic_library_interesting import com.into.websoso.core.resource.R.drawable.ic_library_null_star import com.into.websoso.core.resource.R.drawable.ic_storage_star import com.into.websoso.feature.library.model.LibraryListItemModel -import com.into.websoso.feature.library.model.RatingStarType import com.into.websoso.feature.library.model.ReadStatus import com.into.websoso.feature.library.util.formatDateRange @@ -47,6 +46,12 @@ private val HORIZONTAL_PADDING = 20.dp private const val IMAGE_ASPECT_WIDTH = 102.67f private const val IMAGE_ASPECT_HEIGHT = 160f +private enum class RatingStarType { + FULL, + HALF, + EMPTY, +} + @Composable fun NovelGridListItem( item: LibraryListItemModel, @@ -90,7 +95,7 @@ fun NovelGridListItem( } @Composable -fun NovelGridThumbnail( +private fun NovelGridThumbnail( item: LibraryListItemModel, size: GridItemSize, ) { @@ -129,7 +134,7 @@ fun NovelGridThumbnail( } @Composable -fun ReadStatusBadge( +private fun ReadStatusBadge( readStatus: ReadStatus, modifier: Modifier = Modifier, ) { @@ -146,7 +151,7 @@ fun ReadStatusBadge( } @Composable -fun rememberGridItemSize(): GridItemSize { +private fun rememberGridItemSize(): GridItemSize { val screenWidth = LocalConfiguration.current.screenWidthDp val density = LocalDensity.current @@ -162,7 +167,7 @@ fun rememberGridItemSize(): GridItemSize { } @Composable -fun NovelRatingStar( +private fun NovelRatingStar( rating: Float, modifier: Modifier = Modifier, ) { @@ -200,7 +205,7 @@ private fun calculateRatingStars(rating: Float): List { } } -data class GridItemSize( +private data class GridItemSize( val width: Dp, val height: Dp, ) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt index 2670d7957..812636a17 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt @@ -83,7 +83,7 @@ fun LibraryFilterTopBar( } @Composable -fun NovelFilterChipSection( +private fun NovelFilterChipSection( libraryFilterUiState: LibraryFilterUiState, onFilterClick: (LibraryFilterType) -> Unit, ) { @@ -134,7 +134,7 @@ fun NovelFilterChipSection( } @Composable -fun NovelFilterChip( +private fun NovelFilterChip( text: String, isSelected: Boolean, onClick: () -> Unit, @@ -174,7 +174,7 @@ fun NovelFilterChip( } @Composable -fun NovelFilterStatusBar( +private fun NovelFilterStatusBar( totalCount: Int, selectedSortType: SortType, isGrid: Boolean, From ded37fb965fef73cad06643dcebd3dd110f635f8 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Thu, 3 Jul 2025 23:14:18 +0900 Subject: [PATCH 16/53] =?UTF-8?q?refactor:=20=EA=B7=B8=EB=A6=AC=EB=93=9C?= =?UTF-8?q?=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EC=A0=80?= =?UTF-8?q?=EB=B8=94=20=ED=95=A8=EC=88=98=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../into/websoso/feature/library/component/LibraryGridList.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt index 82463ad89..847c52e30 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.unit.dp import com.into.websoso.feature.library.model.LibraryListItemModel @Composable -fun NovelGridList( +fun LibraryGridList( novels: List, gridState: LazyGridState, modifier: Modifier = Modifier, From 06b122ffcdf134b26703a1d84365acd076c9e76c Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Fri, 4 Jul 2025 23:01:01 +0900 Subject: [PATCH 17/53] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?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 --- .../library/component/LibraryGridListItem.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt index 9ff3b44a3..214ab9af7 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt @@ -80,7 +80,7 @@ fun NovelGridListItem( overflow = TextOverflow.Ellipsis, ) - item.myRating?.let { + item.userNovelRating?.let { NovelRatingStar(rating = it) } @@ -105,7 +105,7 @@ private fun NovelGridThumbnail( .clip(RoundedCornerShape(8.dp)), ) { AsyncImage( - model = item.novelImageUrl, + model = item.novelImage, contentDescription = item.title, contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize(), @@ -120,7 +120,7 @@ private fun NovelGridThumbnail( ) } - if (item.isInterested) { + if (item.isInterest) { Image( imageVector = ImageVector.vectorResource(id = ic_library_interesting), contentDescription = null, @@ -186,12 +186,14 @@ private fun NovelRatingStar( } @Composable -private fun ratingStarIcon(starType: RatingStarType): ImageVector = - when (starType) { - RatingStarType.FULL -> ImageVector.vectorResource(id = ic_storage_star) - RatingStarType.HALF -> ImageVector.vectorResource(id = ic_library_half_star) - RatingStarType.EMPTY -> ImageVector.vectorResource(id = ic_library_null_star) +private fun ratingStarIcon(starType: RatingStarType): ImageVector { + val resId = when (starType) { + RatingStarType.FULL -> ic_storage_star + RatingStarType.HALF -> ic_library_half_star + RatingStarType.EMPTY -> ic_library_null_star } + return ImageVector.vectorResource(id = resId) +} private fun calculateRatingStars(rating: Float): List { val fullStar = rating.toInt() From 9c72ab4b90e7cad087b14fdbcf4e30491414ded1 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Fri, 4 Jul 2025 23:01:37 +0900 Subject: [PATCH 18/53] =?UTF-8?q?feat:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=BB=B4=ED=8F=AC=EC=A0=80?= =?UTF-8?q?=EB=B8=94=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibraryListItem.kt | 429 ++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt new file mode 100644 index 000000000..c77056a59 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt @@ -0,0 +1,429 @@ +package com.into.websoso.feature.library.component + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +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.lazy.LazyRow +import androidx.compose.foundation.lazy.items +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.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.into.websoso.core.designsystem.theme.Black +import com.into.websoso.core.designsystem.theme.Gray200 +import com.into.websoso.core.designsystem.theme.Gray300 +import com.into.websoso.core.designsystem.theme.Gray50 +import com.into.websoso.core.designsystem.theme.Primary100 +import com.into.websoso.core.designsystem.theme.Primary20 +import com.into.websoso.core.designsystem.theme.Primary50 +import com.into.websoso.core.designsystem.theme.Secondary100 +import com.into.websoso.core.designsystem.theme.WebsosoTheme +import com.into.websoso.core.designsystem.theme.White +import com.into.websoso.core.resource.R.drawable.ic_library_character +import com.into.websoso.core.resource.R.drawable.ic_library_interesting +import com.into.websoso.core.resource.R.drawable.ic_library_material +import com.into.websoso.core.resource.R.drawable.ic_library_quote_finished +import com.into.websoso.core.resource.R.drawable.ic_library_quote_started +import com.into.websoso.core.resource.R.drawable.ic_library_relationship +import com.into.websoso.core.resource.R.drawable.ic_library_vibe +import com.into.websoso.core.resource.R.drawable.ic_library_world_view +import com.into.websoso.core.resource.R.drawable.ic_storage_star +import com.into.websoso.feature.library.R.string.library_dot_separator +import com.into.websoso.feature.library.R.string.library_my_rating +import com.into.websoso.feature.library.R.string.library_my_rating_score +import com.into.websoso.feature.library.R.string.library_total_rating +import com.into.websoso.feature.library.model.AttractivePoint +import com.into.websoso.feature.library.model.LibraryListItemModel +import com.into.websoso.feature.library.model.ReadStatus +import com.into.websoso.feature.library.util.formatDateRange + +private const val THUMBNAIL_WIDTH_RATIO = 60f / 360f +private const val THUMBNAIL_HEIGHT_RATIO = 80f / 360f +private const val FEED_CARD_WIDTH_RATIO = 0.8611f + +@Composable +fun LibraryListItem( + item: LibraryListItemModel, + modifier: Modifier = Modifier, + onClick: () -> Unit = {}, +) { + Column( + modifier = modifier + .fillMaxWidth() + .clickable { onClick() }, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + NovelThumbnail( + thumbnailUrl = item.novelImage, + readStatus = item.readStatus, + isInteresting = item.isInterest, + ) + + NovelInfo( + startDate = item.startDate, + endDate = item.endDate, + title = item.title, + myRating = item.userNovelRating, + totalRating = item.novelRating, + attractivePoints = item.attractivePoints, + ) + } + + if (item.keywords.isNotEmpty()) { + NovelKeywordChipGroup(novelKeyword = item.keywords) + } + + item.myFeeds + .filter { it.isNotBlank() } + .takeIf { it.isNotEmpty() } + ?.let { myFeeds -> + MyFeedCardGroup(myFeeds = myFeeds) + } + + HorizontalDivider( + modifier = Modifier.padding(top = 16.dp), + thickness = 1.dp, + color = Gray50, + ) + } +} + +@Composable +private fun NovelThumbnail( + thumbnailUrl: String, + readStatus: ReadStatus?, + isInteresting: Boolean, +) { + val size = calculateThumbnailSize() + + Column(modifier = Modifier.width(size.width)) { + ReadStatusBadge( + readStatus = readStatus, + width = size.width, + ) + + Spacer(modifier = Modifier.height(6.dp)) + + Box( + modifier = Modifier + .size(width = size.width, height = size.height) + .clip(RoundedCornerShape(8.dp)), + ) { + AsyncImage( + model = thumbnailUrl, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + + if (isInteresting) { + Image( + imageVector = ImageVector.vectorResource(id = ic_library_interesting), + contentDescription = null, + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(4.dp) + .size(16.dp), + ) + } + } + } +} + +@Composable +private fun ReadStatusBadge( + readStatus: ReadStatus?, + width: Dp, +) { + if (readStatus != null) { + Box( + modifier = Modifier + .width(width) + .background( + color = readStatus.backgroundColor, + shape = RoundedCornerShape(8.dp), + ).padding(vertical = 4.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = readStatus.label, + color = White, + style = WebsosoTheme.typography.label2, + ) + } + } +} + +@Composable +private fun calculateThumbnailSize(): ThumbnailUiSize { + val screenWidth = LocalConfiguration.current.screenWidthDp.dp + return ThumbnailUiSize( + width = screenWidth * THUMBNAIL_WIDTH_RATIO, + height = screenWidth * THUMBNAIL_HEIGHT_RATIO, + ) +} + +@Composable +private fun NovelInfo( + startDate: String?, + endDate: String?, + title: String, + myRating: Float?, + totalRating: Float, + attractivePoints: List, +) { + Column { + Spacer(modifier = Modifier.height(2.dp)) + + formatDateRange(startDate, endDate)?.let { + Text( + text = it, + style = WebsosoTheme.typography.body5, + color = Gray300, + ) + } + + Spacer(modifier = Modifier.height(12.dp)) + + Text( + text = title, + style = WebsosoTheme.typography.title2, + color = Black, + ) + + Spacer(modifier = Modifier.height(4.dp)) + + NovelRatings( + myRating = myRating, + totalRating = totalRating, + ) + + Spacer(modifier = Modifier.height(10.dp)) + + AttractivePointTags(types = attractivePoints) + } +} + +@Composable +private fun NovelRatings( + myRating: Float?, + totalRating: Float, +) { + Row(verticalAlignment = Alignment.CenterVertically) { + myRating?.let { + MyRatingSection(rating = it) + Spacer(modifier = Modifier.width(10.dp)) + } + TotalRatingSection(rating = totalRating) + } +} + +@Composable +private fun MyRatingSection(rating: Float) { + Image( + imageVector = ImageVector.vectorResource(id = ic_storage_star), + contentDescription = null, + modifier = Modifier.size(10.dp), + ) + + Spacer(modifier = Modifier.width(2.dp)) + + Text( + text = stringResource(id = library_my_rating_score, rating), + style = WebsosoTheme.typography.body5Secondary, + color = Secondary100, + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Text( + text = stringResource(id = library_my_rating), + style = WebsosoTheme.typography.body5, + color = Gray300, + ) +} + +@Composable +private fun TotalRatingSection(rating: Float) { + Icon( + imageVector = ImageVector.vectorResource(id = ic_storage_star), + contentDescription = null, + modifier = Modifier.size(10.dp), + tint = Gray200, + ) + + Spacer(modifier = Modifier.width(2.dp)) + + Text( + text = stringResource(id = library_total_rating, rating), + style = WebsosoTheme.typography.body5, + color = Gray200, + ) +} + +@Composable +private fun AttractivePointTags(types: List) { + Row(verticalAlignment = Alignment.CenterVertically) { + types.forEachIndexed { index, type -> + AttractivePointItem(type) + + if (index < types.lastIndex) { + Spacer(modifier = Modifier.width(6.dp)) + + Text( + text = stringResource(id = library_dot_separator), + style = WebsosoTheme.typography.body4, + color = Primary100, + ) + + Spacer(modifier = Modifier.width(6.dp)) + } + } + } +} + +@Composable +private fun AttractivePointItem(type: AttractivePoint) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + imageVector = attractivePointIcon(type), + contentDescription = type.label, + modifier = Modifier.size(16.dp), + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Text( + text = type.label, + style = WebsosoTheme.typography.body4, + color = Gray300, + ) + } +} + +@Composable +private fun attractivePointIcon(type: AttractivePoint): ImageVector { + val resId = when (type) { + AttractivePoint.CHARACTER -> ic_library_character + AttractivePoint.MATERIAL -> ic_library_material + AttractivePoint.WORLDVIEW -> ic_library_world_view + AttractivePoint.RELATIONSHIP -> ic_library_relationship + AttractivePoint.VIBE -> ic_library_vibe + } + return ImageVector.vectorResource(id = resId) +} + +@Composable +private fun NovelKeywordChipGroup(novelKeyword: List) { + LazyRow( + contentPadding = PaddingValues(end = 20.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(novelKeyword) { + NovelKeywordChip(it) + } + } +} + +@Composable +private fun NovelKeywordChip(keyword: String) { + Text( + text = keyword, + style = WebsosoTheme.typography.body5, + color = Gray200, + modifier = Modifier + .background(color = Primary20, shape = RoundedCornerShape(20.dp)) + .padding(horizontal = 8.dp, vertical = 6.dp), + ) +} + +@Composable +private fun MyFeedCardGroup(myFeeds: List) { + LazyRow( + modifier = Modifier + .fillMaxWidth(), + contentPadding = PaddingValues(end = 20.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + items(myFeeds, key = { it }) { myFeed -> + MyFeedCard(myFeed) + } + } +} + +@Composable +private fun MyFeedCard(myFeed: String) { + val screenWidth = LocalConfiguration.current.screenWidthDp.dp + val cardWidth = screenWidth * FEED_CARD_WIDTH_RATIO + + Box( + modifier = Modifier + .width(cardWidth) + .height(54.dp) + .background(color = Primary50, shape = RoundedCornerShape(8.dp)) + .padding(horizontal = 12.dp, vertical = 8.dp), + ) { + Image( + imageVector = ImageVector.vectorResource(id = ic_library_quote_started), + contentDescription = null, + modifier = Modifier + .size(20.dp) + .align(Alignment.TopStart), + ) + + Text( + text = myFeed, + style = WebsosoTheme.typography.body5, + color = Black, + textAlign = TextAlign.Center, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .padding(horizontal = 24.dp), + ) + + Image( + imageVector = ImageVector.vectorResource(id = ic_library_quote_finished), + contentDescription = null, + modifier = Modifier + .size(20.dp) + .align(Alignment.BottomEnd), + ) + } +} + +private data class ThumbnailUiSize( + val width: Dp, + val height: Dp, +) From 140cba22155def77483eda76f46fc77e606d2b03 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Fri, 4 Jul 2025 23:01:52 +0900 Subject: [PATCH 19/53] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/component/LibraryList.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt new file mode 100644 index 000000000..5a44cda24 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt @@ -0,0 +1,33 @@ +package com.into.websoso.feature.library.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.into.websoso.feature.library.model.LibraryListItemModel + +@Composable +fun LibraryList( + novels: List, + listState: LazyListState, + modifier: Modifier = Modifier, + onItemClick: (LibraryListItemModel) -> Unit = {}, +) { + LazyColumn( + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(start = 20.dp, bottom = 20.dp), + verticalArrangement = Arrangement.spacedBy(28.dp), + ) { + items(novels, key = { it.title }) { novel -> + LibraryListItem( + item = novel, + onClick = { onItemClick(novel) }, + ) + } + } +} From e0b3a53a4bbc5761b06f1499b924fb4e455906c1 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Fri, 4 Jul 2025 23:03:53 +0900 Subject: [PATCH 20/53] =?UTF-8?q?feat:=20=EB=A7=A4=EB=A0=A5=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=9D=B4=EB=84=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/model/AttractivePoint.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePoint.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePoint.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePoint.kt new file mode 100644 index 000000000..68ee8da92 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePoint.kt @@ -0,0 +1,17 @@ +package com.into.websoso.feature.library.model + +enum class AttractivePoint( + val label: String, + val key: String, +) { + WORLDVIEW("세계관", "worldview"), + MATERIAL("소재", "material"), + CHARACTER("캐릭터", "character"), + RELATIONSHIP("관계", "relationship"), + VIBE("분위기", "vibe"), + ; + + companion object { + fun from(key: String?): AttractivePoint? = entries.find { it.key == key } + } +} From 850022dadff0eb1abf266ad4d99ffa0d739bf635 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Fri, 4 Jul 2025 23:05:03 +0900 Subject: [PATCH 21/53] =?UTF-8?q?feat:=20=EC=86=8C=EC=84=A4=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20=ED=83=80=EC=9E=85=20=EC=9D=B4=EB=84=98=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../websoso/feature/library/model/LibraryFilterType.kt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterType.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterType.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterType.kt new file mode 100644 index 000000000..fff3f1da4 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterType.kt @@ -0,0 +1,8 @@ +package com.into.websoso.feature.library.model + +enum class LibraryFilterType { + Interest, + ReadStatus, + Rating, + AttractivePoint, +} From f491fc91b087bb12e467935f44908184ec31ff1c Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Fri, 4 Jul 2025 23:05:21 +0900 Subject: [PATCH 22/53] =?UTF-8?q?feat:=20=EC=86=8C=EC=84=A4=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=20UI=20state=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/model/LibraryFilterUiState.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiState.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiState.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiState.kt new file mode 100644 index 000000000..459376f2e --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiState.kt @@ -0,0 +1,11 @@ +package com.into.websoso.feature.library.model + +data class LibraryFilterUiState( + val isInterested: Boolean = false, + val readStatusLabel: List = emptyList(), + val readStatusSelected: Boolean = false, + val ratingLabel: List = emptyList(), + val ratingSelected: Boolean = false, + val attractivePointLabel: List = emptyList(), + val attractivePointSelected: Boolean = false, +) From 08289c708e8b63aff68759c22c3c9851d4143cb6 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:07:09 +0900 Subject: [PATCH 23/53] =?UTF-8?q?refactor:=20=EC=9D=BD=EA=B8=B0=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20ReadStatusUi?= =?UTF-8?q?Model=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 --- .../websoso/feature/library/component/LibraryGridListItem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt index 214ab9af7..863389a9a 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt @@ -37,7 +37,7 @@ import com.into.websoso.core.resource.R.drawable.ic_library_interesting import com.into.websoso.core.resource.R.drawable.ic_library_null_star import com.into.websoso.core.resource.R.drawable.ic_storage_star import com.into.websoso.feature.library.model.LibraryListItemModel -import com.into.websoso.feature.library.model.ReadStatus +import com.into.websoso.feature.library.model.ReadStatusUiModel import com.into.websoso.feature.library.util.formatDateRange private const val GRID_COLUMN_COUNT = 3 @@ -135,7 +135,7 @@ private fun NovelGridThumbnail( @Composable private fun ReadStatusBadge( - readStatus: ReadStatus, + readStatus: ReadStatusUiModel, modifier: Modifier = Modifier, ) { Text( From bb15b1311394b15927665913e8ac8a766bb8a029 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:17:23 +0900 Subject: [PATCH 24/53] =?UTF-8?q?delete:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/model/AttractivePoint.kt | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePoint.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePoint.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePoint.kt deleted file mode 100644 index 68ee8da92..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePoint.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.into.websoso.feature.library.model - -enum class AttractivePoint( - val label: String, - val key: String, -) { - WORLDVIEW("세계관", "worldview"), - MATERIAL("소재", "material"), - CHARACTER("캐릭터", "character"), - RELATIONSHIP("관계", "relationship"), - VIBE("분위기", "vibe"), - ; - - companion object { - fun from(key: String?): AttractivePoint? = entries.find { it.key == key } - } -} From 80275059e7e451ef5e59e74df66f05c62c02cd39 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:17:56 +0900 Subject: [PATCH 25/53] =?UTF-8?q?feat:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A7=A4=EB=A0=A5=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=9D=B4?= =?UTF-8?q?=EB=84=98=EC=97=90=20=EB=9D=BC=EB=B2=A8=EA=B3=BC=20=ED=82=A4=20?= =?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 --- .../domain/library/model/AttractivePoints.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt b/domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt index ee4c3c651..2f5441167 100644 --- a/domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt +++ b/domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoints.kt @@ -1,9 +1,12 @@ package com.into.websoso.domain.library.model -enum class AttractivePoints { - WORLDVIEW, - MATERIAL, - CHARACTER, - RELATIONSHIP, - VIBE, +enum class AttractivePoints( + val label: String, + val key: String, +) { + WORLDVIEW("세계관", "worldview"), + MATERIAL("소재", "material"), + CHARACTER("캐릭터", "character"), + RELATIONSHIP("관계", "relationship"), + VIBE("분위기", "vibe"), } From 8f2bc051ea9f941960cb93d97e32ec93d02ae3e5 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:19:48 +0900 Subject: [PATCH 26/53] =?UTF-8?q?feat:=20=EB=A7=A4=EB=A0=A5=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20ui=20=EB=AA=A8=EB=8D=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/model/AttractivePointUiModel.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePointUiModel.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePointUiModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePointUiModel.kt new file mode 100644 index 000000000..571dd4f85 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePointUiModel.kt @@ -0,0 +1,9 @@ +package com.into.websoso.feature.library.model + +import com.into.websoso.domain.library.model.AttractivePoints + +data class AttractivePointUiModel( + val type: AttractivePoints, + val label: String, + val key: String, +) From 715c1b349c0db6bb0fffba9deab3357399e45a1a Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:20:03 +0900 Subject: [PATCH 27/53] =?UTF-8?q?feat:=20=EB=A7=A4=EB=A0=A5=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20ui=20=EB=AA=A8=EB=8D=B8=EA=B3=BC=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EB=A7=B5=ED=95=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/mapper/AttractivePointsMapper.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/mapper/AttractivePointsMapper.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/mapper/AttractivePointsMapper.kt b/feature/library/src/main/java/com/into/websoso/feature/library/mapper/AttractivePointsMapper.kt new file mode 100644 index 000000000..8008f4eb4 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/mapper/AttractivePointsMapper.kt @@ -0,0 +1,12 @@ +package com.into.websoso.feature.library.mapper + +import com.into.websoso.domain.library.model.AttractivePoints +import com.into.websoso.feature.library.model.AttractivePointUiModel + +fun AttractivePoints.toUiModel(): AttractivePointUiModel { + return AttractivePointUiModel( + type = this, + label = this.label, + key = this.key, + ) +} From 9b2be9328013a109c2c643f1c6dd0300ddfa0e13 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:20:18 +0900 Subject: [PATCH 28/53] =?UTF-8?q?feat:=20=EB=82=A0=EC=A7=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=85=A1=20=EC=9C=A0=ED=8B=B8=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/util/DateFormatter.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/util/DateFormatter.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/util/DateFormatter.kt b/feature/library/src/main/java/com/into/websoso/feature/library/util/DateFormatter.kt new file mode 100644 index 000000000..15a3cb064 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/util/DateFormatter.kt @@ -0,0 +1,30 @@ +package com.into.websoso.feature.library.util + +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException + +private val INPUT_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd") +private val OUTPUT_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd") + +fun formatDateRange( + startDate: String?, + endDate: String?, +): String? { + if (startDate.isNullOrBlank()) return null + + return try { + val startFormatted = + LocalDate.parse(startDate, INPUT_DATE_FORMATTER).format(OUTPUT_DATE_FORMATTER) + + if (endDate.isNullOrBlank()) { + startFormatted + } else { + val endFormatted = + LocalDate.parse(endDate, INPUT_DATE_FORMATTER).format(OUTPUT_DATE_FORMATTER) + "$startFormatted ~ $endFormatted" + } + } catch (e: DateTimeParseException) { + null + } +} From 356a1bca5cb62610b766dba5fde110b85528739f Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:20:36 +0900 Subject: [PATCH 29/53] =?UTF-8?q?feat:=20=ED=95=84=ED=84=B0=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8=20=ED=8F=AC=EB=A7=B7=ED=8C=85=20=EC=9C=A0=ED=8B=B8=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/util/FilterLabelFormatter.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/util/FilterLabelFormatter.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/util/FilterLabelFormatter.kt b/feature/library/src/main/java/com/into/websoso/feature/library/util/FilterLabelFormatter.kt new file mode 100644 index 000000000..f3e5646c4 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/util/FilterLabelFormatter.kt @@ -0,0 +1,11 @@ +package com.into.websoso.feature.library.util + +fun buildFilterLabel( + defaultLabel: String, + values: List, +): String = + when { + values.isEmpty() -> defaultLabel + values.size == 1 -> values.first() + else -> "${values.first()} 외 ${values.size - 1}개" + } From 120bb1a1231f6cb318712437f8961f118e780840 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:25:46 +0900 Subject: [PATCH 30/53] =?UTF-8?q?feat:=20=EC=A0=95=EB=A0=AC=ED=83=80?= =?UTF-8?q?=EC=9E=85=20UI=20=EB=AA=A8=EB=8D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/model/SortTypeUiModel.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt new file mode 100644 index 000000000..8b66b4348 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt @@ -0,0 +1,18 @@ +package com.into.websoso.feature.library.model + +import com.into.websoso.domain.library.model.SortType + +enum class SortTypeUiModel( + val sortType: SortType, + val key: String, + val displayName: String, +) { + NEWEST(SortType.NEWEST, "NEWEST", "최신 순"), + OLDEST(SortType.OLDEST, "OLDEST", "오래된 순"); + + companion object { + fun from(sortType: SortType): SortTypeUiModel { + return entries.firstOrNull { it.sortType == sortType } ?: NEWEST + } + } +} From 0580d0c5baef1a172fefac347942df3e19e12a99 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:40:17 +0900 Subject: [PATCH 31/53] =?UTF-8?q?feat:=20=EC=8A=A4=ED=8A=B8=EB=A7=81=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/library/src/main/res/values/string.xml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 feature/library/src/main/res/values/string.xml diff --git a/feature/library/src/main/res/values/string.xml b/feature/library/src/main/res/values/string.xml new file mode 100644 index 000000000..31ebaf045 --- /dev/null +++ b/feature/library/src/main/res/values/string.xml @@ -0,0 +1,15 @@ + + + 관심 + %1$d개 + 평점 + 읽기 상태 + 매력포인트 + 내 별점 + %1$.1f + %1$.1f 전체 별점 + + 정렬 + 보관함이 비어있어요 + 웹소설 찾으러 가기 + From 23ef2df1b8895175107194103163e925d8b38dd5 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:41:29 +0900 Subject: [PATCH 32/53] =?UTF-8?q?delete:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=B6=80=EB=B6=84=20=EC=A3=BC=EC=84=9D=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../websoso/data/library/LibraryRepository.kt | 3 +- .../datasource/LibraryLocalDataSource.kt | 34 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt b/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt index a07bfbb34..1161b92ef 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt @@ -3,7 +3,6 @@ package com.into.websoso.data.library import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData -import com.into.websoso.data.library.datasource.LibraryLocalDataSource import com.into.websoso.data.library.datasource.LibraryRemoteDataSource import com.into.websoso.data.library.model.NovelEntity import kotlinx.coroutines.flow.Flow @@ -15,7 +14,7 @@ class LibraryRepository @Inject constructor( private val libraryRemoteDataSource: LibraryRemoteDataSource, - private val libraryLocalDataSource: LibraryLocalDataSource, +// private val libraryLocalDataSource: LibraryLocalDataSource, ) { fun getUserLibrary( userId: Long, diff --git a/data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt b/data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt index 2930502cf..be425be2a 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt @@ -3,20 +3,20 @@ package com.into.websoso.data.library.datasource import com.into.websoso.data.library.model.NovelEntity import kotlinx.coroutines.flow.Flow -interface LibraryLocalDataSource { - suspend fun insertNovels(novels: List) - - suspend fun insertNovel(novel: NovelEntity) - - fun selectAllNovels(): Flow> - - suspend fun selectNovel(novelId: Long): NovelEntity? - - suspend fun updateNovels(novels: List) - - suspend fun updateNovel(novel: NovelEntity) - - suspend fun deleteAllNovels() - - suspend fun deleteNovel(novelId: Long) -} +//interface LibraryLocalDataSource { +// suspend fun insertNovels(novels: List) +// +// suspend fun insertNovel(novel: NovelEntity) +// +// fun selectAllNovels(): Flow> +// +// suspend fun selectNovel(novelId: Long): NovelEntity? +// +// suspend fun updateNovels(novels: List) +// +// suspend fun updateNovel(novel: NovelEntity) +// +// suspend fun deleteAllNovels() +// +// suspend fun deleteNovel(novelId: Long) +//} From c46b8e70e1b65a05f61fd40c5b3f39d1f07f279a Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:42:10 +0900 Subject: [PATCH 33/53] =?UTF-8?q?build:=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/library/build.gradle.kts | 4 ++++ gradle/libs.versions.toml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/feature/library/build.gradle.kts b/feature/library/build.gradle.kts index 3d0808734..6aba7a1ae 100644 --- a/feature/library/build.gradle.kts +++ b/feature/library/build.gradle.kts @@ -9,8 +9,12 @@ android { } dependencies { + implementation(projects.data.library) + implementation(projects.core.resource) + implementation(projects.core.designsystem) implementation(projects.domain.library) implementation(projects.data.library) implementation("androidx.paging:paging-runtime:3.3.2") + implementation(libs.androidx.paging.compose.android) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index efaa0a5e2..4203b0810 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -67,6 +67,7 @@ ktlint = "12.1.0" compose-bom = "2024.12.01" compose-compiler = "1.5.2" compose-ui = "1.7.6" +pagingComposeAndroid = "3.3.6" [plugins] android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } @@ -162,6 +163,7 @@ compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } compose-hilt-navigation = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hilt-navigation-compose" } lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose" } +androidx-paging-compose-android = { group = "androidx.paging", name = "paging-compose-android", version.ref = "pagingComposeAndroid" } [bundles] compose = [ From 7ef7c62bad6e9565969646f7000f3ed8e9915aca Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:42:39 +0900 Subject: [PATCH 34/53] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20uiState=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../into/websoso/feature/library/model/LibraryUiState.kt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt new file mode 100644 index 000000000..6a093d256 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryUiState.kt @@ -0,0 +1,8 @@ +package com.into.websoso.feature.library.model + +data class LibraryUiState( + val novels: List = emptyList(), + val isGrid: Boolean = false, + val selectedSortType: SortTypeUiModel = SortTypeUiModel.NEWEST, + val filterUiState: LibraryFilterUiState = LibraryFilterUiState(), +) From f7b50bab02893979b9e23cb3d2b21d1f0e6159b3 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:52:15 +0900 Subject: [PATCH 35/53] =?UTF-8?q?feat:=20=ED=8E=98=EC=9D=B4=EC=A7=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 --- .../library/component/LibraryGridList.kt | 26 ++++++++++++++----- .../feature/library/component/LibraryList.kt | 19 ++++++++------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt index 847c52e30..aad1f3ca8 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt @@ -3,18 +3,22 @@ package com.into.websoso.feature.library.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems import com.into.websoso.feature.library.model.LibraryListItemModel @Composable fun LibraryGridList( - novels: List, + novels: LazyPagingItems, gridState: LazyGridState, modifier: Modifier = Modifier, onItemClick: (LibraryListItemModel) -> Unit = {}, @@ -27,11 +31,19 @@ fun LibraryGridList( horizontalArrangement = Arrangement.spacedBy(6.dp), verticalArrangement = Arrangement.spacedBy(18.dp), ) { - items(novels, key = { it.title }) { novel -> - NovelGridListItem( - item = novel, - onItemClick = { onItemClick(novel) }, - ) + items(novels.itemCount) { index -> + novels[index]?.let { novel -> + NovelGridListItem( + item = novel, + onItemClick = { onItemClick(novel) }, + ) + } + } + + item(span = { GridItemSpan(maxLineSpan) }) { + if (novels.loadState.append is LoadState.Loading) { + CircularProgressIndicator(modifier = Modifier.padding(16.dp)) + } } } } diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt index 5a44cda24..a022f3c50 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt @@ -5,29 +5,32 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.paging.compose.LazyPagingItems import com.into.websoso.feature.library.model.LibraryListItemModel @Composable fun LibraryList( - novels: List, + novels: LazyPagingItems, listState: LazyListState, - modifier: Modifier = Modifier, onItemClick: (LibraryListItemModel) -> Unit = {}, + modifier: Modifier = Modifier, ) { LazyColumn( modifier = modifier.fillMaxSize(), + state = listState, contentPadding = PaddingValues(start = 20.dp, bottom = 20.dp), verticalArrangement = Arrangement.spacedBy(28.dp), ) { - items(novels, key = { it.title }) { novel -> - LibraryListItem( - item = novel, - onClick = { onItemClick(novel) }, - ) + items(novels.itemCount) { index -> + novels[index]?.let { novel -> + LibraryListItem( + item = novel, + onClick = { onItemClick(novel) }, + ) + } } } } From 72a2754b31ece112b2316c23327a6943a5348d0b Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:52:52 +0900 Subject: [PATCH 36/53] =?UTF-8?q?refactor:=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibraryListItem.kt | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt index c77056a59..af8fee32e 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt @@ -54,13 +54,14 @@ import com.into.websoso.core.resource.R.drawable.ic_library_relationship import com.into.websoso.core.resource.R.drawable.ic_library_vibe import com.into.websoso.core.resource.R.drawable.ic_library_world_view import com.into.websoso.core.resource.R.drawable.ic_storage_star +import com.into.websoso.domain.library.model.AttractivePoints import com.into.websoso.feature.library.R.string.library_dot_separator import com.into.websoso.feature.library.R.string.library_my_rating import com.into.websoso.feature.library.R.string.library_my_rating_score import com.into.websoso.feature.library.R.string.library_total_rating -import com.into.websoso.feature.library.model.AttractivePoint +import com.into.websoso.feature.library.model.AttractivePointUiModel import com.into.websoso.feature.library.model.LibraryListItemModel -import com.into.websoso.feature.library.model.ReadStatus +import com.into.websoso.feature.library.model.ReadStatusUiModel import com.into.websoso.feature.library.util.formatDateRange private const val THUMBNAIL_WIDTH_RATIO = 60f / 360f @@ -120,7 +121,7 @@ fun LibraryListItem( @Composable private fun NovelThumbnail( thumbnailUrl: String, - readStatus: ReadStatus?, + readStatus: ReadStatusUiModel?, isInteresting: Boolean, ) { val size = calculateThumbnailSize() @@ -161,7 +162,7 @@ private fun NovelThumbnail( @Composable private fun ReadStatusBadge( - readStatus: ReadStatus?, + readStatus: ReadStatusUiModel?, width: Dp, ) { if (readStatus != null) { @@ -199,7 +200,7 @@ private fun NovelInfo( title: String, myRating: Float?, totalRating: Float, - attractivePoints: List, + attractivePoints: List, ) { Column { Spacer(modifier = Modifier.height(2.dp)) @@ -291,7 +292,7 @@ private fun TotalRatingSection(rating: Float) { } @Composable -private fun AttractivePointTags(types: List) { +private fun AttractivePointTags(types: List) { Row(verticalAlignment = Alignment.CenterVertically) { types.forEachIndexed { index, type -> AttractivePointItem(type) @@ -312,7 +313,7 @@ private fun AttractivePointTags(types: List) { } @Composable -private fun AttractivePointItem(type: AttractivePoint) { +private fun AttractivePointItem(type: AttractivePointUiModel) { Row(verticalAlignment = Alignment.CenterVertically) { Image( imageVector = attractivePointIcon(type), @@ -331,13 +332,13 @@ private fun AttractivePointItem(type: AttractivePoint) { } @Composable -private fun attractivePointIcon(type: AttractivePoint): ImageVector { - val resId = when (type) { - AttractivePoint.CHARACTER -> ic_library_character - AttractivePoint.MATERIAL -> ic_library_material - AttractivePoint.WORLDVIEW -> ic_library_world_view - AttractivePoint.RELATIONSHIP -> ic_library_relationship - AttractivePoint.VIBE -> ic_library_vibe +private fun attractivePointIcon(points: AttractivePointUiModel): ImageVector { + val resId = when (points.type) { + AttractivePoints.CHARACTER -> ic_library_character + AttractivePoints.MATERIAL -> ic_library_material + AttractivePoints.WORLDVIEW -> ic_library_world_view + AttractivePoints.RELATIONSHIP -> ic_library_relationship + AttractivePoints.VIBE -> ic_library_vibe } return ImageVector.vectorResource(id = resId) } From f7df375e5604da3fe56b36ca654c2cdd270a4165 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:53:53 +0900 Subject: [PATCH 37/53] =?UTF-8?q?delete:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=9E=84=ED=8F=AC=ED=8A=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/library/datasource/LibraryLocalDataSource.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt b/data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt index be425be2a..031476dfb 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/datasource/LibraryLocalDataSource.kt @@ -1,9 +1,6 @@ package com.into.websoso.data.library.datasource -import com.into.websoso.data.library.model.NovelEntity -import kotlinx.coroutines.flow.Flow - -//interface LibraryLocalDataSource { +// interface LibraryLocalDataSource { // suspend fun insertNovels(novels: List) // // suspend fun insertNovel(novel: NovelEntity) @@ -19,4 +16,4 @@ import kotlinx.coroutines.flow.Flow // suspend fun deleteAllNovels() // // suspend fun deleteNovel(novelId: Long) -//} +// } From f59ee79ad4cd78d4b49b7027349668bc32b26644 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:54:05 +0900 Subject: [PATCH 38/53] =?UTF-8?q?build:=20=EB=B2=84=EC=A0=84=20=EC=97=85?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/library/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/library/build.gradle.kts b/feature/library/build.gradle.kts index 6aba7a1ae..f4f50ecf3 100644 --- a/feature/library/build.gradle.kts +++ b/feature/library/build.gradle.kts @@ -15,6 +15,6 @@ dependencies { implementation(projects.domain.library) implementation(projects.data.library) - implementation("androidx.paging:paging-runtime:3.3.2") + implementation("androidx.paging:paging-runtime:3.3.6") implementation(libs.androidx.paging.compose.android) } From 7304feb6e85210942099f094a786f91664725d15 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:54:34 +0900 Subject: [PATCH 39/53] =?UTF-8?q?feat:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=ED=85=9C=20=EB=AA=A8=EB=8D=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/model/LibraryListItemModel.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt new file mode 100644 index 000000000..160141435 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt @@ -0,0 +1,16 @@ +package com.into.websoso.feature.library.model + +data class LibraryListItemModel( + val novelId: Long, + val title: String, + val startDate: String?, + val endDate: String?, + val novelImage: String, + val readStatus: ReadStatusUiModel?, + val userNovelRating: Float?, + val novelRating: Float, + val attractivePoints: List, + val keywords: List, + val myFeeds: List, + val isInterest: Boolean, +) From e9c7a5cdce3c3d72b2a568de6383514e17350173 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:56:22 +0900 Subject: [PATCH 40/53] =?UTF-8?q?feat:=20=EC=A0=95=EB=A0=AC=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=EC=8B=9C=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibrayFilterTopBar.kt | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt index 812636a17..463d5240c 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize @@ -39,7 +40,6 @@ import com.into.websoso.core.resource.R.drawable.ic_library_drop_down_fill import com.into.websoso.core.resource.R.drawable.ic_library_grid import com.into.websoso.core.resource.R.drawable.ic_library_list import com.into.websoso.core.resource.R.drawable.ic_library_sort -import com.into.websoso.domain.library.model.SortType import com.into.websoso.feature.library.R.string.library_attractive_point import com.into.websoso.feature.library.R.string.library_interesting import com.into.websoso.feature.library.R.string.library_novel_count @@ -47,6 +47,7 @@ import com.into.websoso.feature.library.R.string.library_rating import com.into.websoso.feature.library.R.string.library_read_status import com.into.websoso.feature.library.model.LibraryFilterType import com.into.websoso.feature.library.model.LibraryFilterUiState +import com.into.websoso.feature.library.model.SortTypeUiModel import com.into.websoso.feature.library.util.buildFilterLabel @Composable @@ -54,7 +55,7 @@ fun LibraryFilterTopBar( libraryFilterUiState: LibraryFilterUiState, totalCount: Int, onFilterClick: (LibraryFilterType) -> Unit, - selectedSortType: SortType, + selectedSortType: SortTypeUiModel, onSortClick: () -> Unit, isGrid: Boolean, onToggleViewType: () -> Unit, @@ -176,7 +177,7 @@ private fun NovelFilterChip( @Composable private fun NovelFilterStatusBar( totalCount: Int, - selectedSortType: SortType, + selectedSortType: SortTypeUiModel, isGrid: Boolean, onSortClick: () -> Unit, onToggleViewType: () -> Unit, @@ -193,29 +194,49 @@ private fun NovelFilterStatusBar( ) Row(verticalAlignment = Alignment.CenterVertically) { - TextButton(onClick = onSortClick) { - Image( - imageVector = ImageVector.vectorResource(id = ic_library_sort), - contentDescription = null, - modifier = Modifier.size(16.dp), - ) - - Text( - text = selectedSortType.displayName, - style = WebsosoTheme.typography.body5, - color = Gray300, - ) - } + SortTypeSelector( + selectedSortType = selectedSortType, + onClick = { onSortClick() }, + ) IconButton(onClick = onToggleViewType) { Image( imageVector = ImageVector.vectorResource( id = if (isGrid) ic_library_grid else ic_library_list, ), - contentDescription = "리스트 형태 전환", + contentDescription = null, modifier = Modifier.size(16.dp), ) } } } } + +@Composable +fun SortTypeSelector( + selectedSortType: SortTypeUiModel, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + TextButton( + onClick = onClick, + modifier = modifier, + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + imageVector = ImageVector.vectorResource(id = ic_library_sort), + contentDescription = null, + modifier = Modifier.size(16.dp), + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Text( + text = selectedSortType.displayName, + style = WebsosoTheme.typography.body5, + color = Gray300, + ) + } + } +} From f800e563b0b00f5b7008b189de2816a657188cd9 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:56:51 +0900 Subject: [PATCH 41/53] =?UTF-8?q?feat:=20=EC=86=8C=EC=84=A4=20Entity?= =?UTF-8?q?=EC=99=80=20=EC=84=9C=EC=9E=AC=EC=95=84=EC=9D=B4=ED=85=9C?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EB=A9=A5=ED=95=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/mapper/ListItemMapper.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/mapper/ListItemMapper.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/mapper/ListItemMapper.kt b/feature/library/src/main/java/com/into/websoso/feature/library/mapper/ListItemMapper.kt new file mode 100644 index 000000000..7876130c0 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/mapper/ListItemMapper.kt @@ -0,0 +1,24 @@ +package com.into.websoso.feature.library.mapper + +import com.into.websoso.data.library.model.NovelEntity +import com.into.websoso.domain.library.model.AttractivePoints +import com.into.websoso.feature.library.model.LibraryListItemModel +import com.into.websoso.feature.library.model.ReadStatusUiModel + +fun NovelEntity.toUiModel(): LibraryListItemModel = + LibraryListItemModel( + novelId = novelId, + title = title, + startDate = startDate, + endDate = endDate, + novelImage = novelImage, + readStatus = ReadStatusUiModel.from(readStatus), + userNovelRating = userNovelRating.takeIf { it > 0f }, + novelRating = novelRating, + attractivePoints = attractivePoints.mapNotNull { key -> + AttractivePoints.entries.find { it.key == key }?.toUiModel() + }, + keywords = keywords, + myFeeds = myFeeds, + isInterest = isInterest, + ) From 4d2d5b9a5f1a141850020e71800e6780eb307d3a Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:57:28 +0900 Subject: [PATCH 42/53] =?UTF-8?q?feat:=20=EC=9D=BD=EA=B8=B0=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=B0=B0=EA=B2=BD?= =?UTF-8?q?=EC=83=89=EA=B3=BC=20=EB=9D=BC=EB=B2=A8=20=EC=9D=B4=EB=84=98?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/mapper/ReadStatusMapper.kt | 11 ++++++++++ .../library/model/ReadStatusUiModel.kt | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/mapper/ReadStatusMapper.kt create mode 100644 feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/mapper/ReadStatusMapper.kt b/feature/library/src/main/java/com/into/websoso/feature/library/mapper/ReadStatusMapper.kt new file mode 100644 index 000000000..655c21ef6 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/mapper/ReadStatusMapper.kt @@ -0,0 +1,11 @@ +package com.into.websoso.feature.library.mapper + +import com.into.websoso.domain.library.model.ReadStatus +import com.into.websoso.feature.library.model.ReadStatusUiModel + +fun ReadStatus.toUiModel(): ReadStatusUiModel = + when (this) { + ReadStatus.WATCHING -> ReadStatusUiModel.WATCHING + ReadStatus.WATCHED -> ReadStatusUiModel.WATCHED + ReadStatus.QUIT -> ReadStatusUiModel.QUIT + } diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt new file mode 100644 index 000000000..be6ead4e1 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/ReadStatusUiModel.kt @@ -0,0 +1,21 @@ +package com.into.websoso.feature.library.model + +import androidx.compose.ui.graphics.Color +import com.into.websoso.core.designsystem.theme.Black +import com.into.websoso.core.designsystem.theme.Gray200 +import com.into.websoso.core.designsystem.theme.Primary100 + +enum class ReadStatusUiModel( + val label: String, + val backgroundColor: Color, + val key: String, +) { + WATCHING("보는중", Primary100, "WATCHING"), + WATCHED("봤어요", Black, "WATCHED"), + QUIT("하차", Gray200, "QUIT"), + ; + + companion object { + fun from(value: String?): ReadStatusUiModel? = entries.find { it.key == value } + } +} From b7a77d7f199fed89b2c582bcf1d4c5be22751b9d Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 11:59:32 +0900 Subject: [PATCH 43/53] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=B0=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../websoso/feature/library/LibraryScreen.kt | 114 ++++++++++++++++-- .../library/src/main/res/values/string.xml | 1 + 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt index 4a9dc5e3f..6353fb0fb 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt @@ -1,24 +1,124 @@ package com.into.websoso.feature.library +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +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.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.paging.LoadState +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.map +import com.into.websoso.core.designsystem.theme.Black +import com.into.websoso.core.designsystem.theme.WebsosoTheme +import com.into.websoso.core.resource.R.drawable.ic_common_search +import com.into.websoso.feature.library.R.string.library_title +import com.into.websoso.feature.library.component.LibraryEmptyView +import com.into.websoso.feature.library.component.LibraryFilterTopBar +import com.into.websoso.feature.library.component.LibraryGridList +import com.into.websoso.feature.library.component.LibraryList +import com.into.websoso.feature.library.mapper.toUiModel +import kotlinx.coroutines.flow.map @Composable fun LibraryScreen(libraryViewModel: LibraryViewModel = hiltViewModel()) { - LaunchedEffect(Unit) { - libraryViewModel.novelList.collect { - // 페이징 + val uiState by libraryViewModel.uiState.collectAsState() + + val isGrid = uiState.isGrid + val filterState = uiState.filterUiState + val sortType = uiState.selectedSortType + + val pagingItems = libraryViewModel.novelPagingData + .map { pagingData -> pagingData.map { it.toUiModel() } } + .collectAsLazyPagingItems() + + val listState = rememberLazyListState() + val gridState = rememberLazyGridState() + + Column(modifier = Modifier.fillMaxSize()) { + LibraryTopBar( + onSearchClick = { + // TODO: 검색 화면으로 이동 + }, + ) + + LibraryFilterTopBar( + libraryFilterUiState = filterState, + totalCount = pagingItems.itemCount, + selectedSortType = sortType, + isGrid = isGrid, + onFilterClick = { type -> libraryViewModel.onFilterClick(type) }, + onSortClick = { libraryViewModel.onSortClick(sortType) }, + onToggleViewType = { libraryViewModel.updateViewType() }, + ) + + Spacer(modifier = Modifier.height(4.dp)) + + when { + pagingItems.itemCount == 0 && pagingItems.loadState.refresh !is LoadState.Loading -> { + LibraryEmptyView( + onExploreClick = { libraryViewModel.navigateToExplore() }, + ) + } + + isGrid -> { + LibraryGridList( + novels = pagingItems, + gridState = gridState, + onItemClick = { libraryViewModel.onItemClick(it) }, + ) + } + + else -> { + LibraryList( + novels = pagingItems, + listState = listState, + onItemClick = { libraryViewModel.onItemClick(it) }, + ) + } } } +} - Column( - modifier = Modifier.fillMaxSize(), +@Composable +fun LibraryTopBar(onSearchClick: () -> Unit = {}) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 10.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { - Text(text = "123123") + Text( + text = stringResource(id = library_title), + style = WebsosoTheme.typography.headline1, + color = Black, + ) + + IconButton(onClick = onSearchClick) { + Image( + imageVector = ImageVector.vectorResource(id = ic_common_search), + contentDescription = "검색", + modifier = Modifier.size(24.dp), + ) + } } } diff --git a/feature/library/src/main/res/values/string.xml b/feature/library/src/main/res/values/string.xml index 31ebaf045..194848d17 100644 --- a/feature/library/src/main/res/values/string.xml +++ b/feature/library/src/main/res/values/string.xml @@ -12,4 +12,5 @@ 정렬 보관함이 비어있어요 웹소설 찾으러 가기 + 서재 From 8332a57ae6fed3402a616b3c79bce50710eaeb71 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 12:00:17 +0900 Subject: [PATCH 44/53] =?UTF-8?q?feat:=20=EC=84=9C=EC=9E=AC=20=EB=B7=B0?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/LibraryViewModel.kt | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt index 6bb310590..f4ccc2a0b 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt @@ -2,16 +2,88 @@ package com.into.websoso.feature.library import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData import androidx.paging.cachedIn +import com.into.websoso.data.library.model.NovelEntity import com.into.websoso.domain.library.GetUserNovelUseCase +import com.into.websoso.domain.library.model.SortType +import com.into.websoso.feature.library.model.LibraryFilterType +import com.into.websoso.feature.library.model.LibraryListItemModel +import com.into.websoso.feature.library.model.LibraryUiState +import com.into.websoso.feature.library.model.SortTypeUiModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.update import javax.inject.Inject @HiltViewModel class LibraryViewModel @Inject constructor( - getUserNovelUseCase: GetUserNovelUseCase, + private val getUserNovelUseCase: GetUserNovelUseCase, ) : ViewModel() { - val novelList = getUserNovelUseCase().cachedIn(viewModelScope) + private val mutableQueryParams = MutableStateFlow(LibraryQueryParams()) + private val queryParams: StateFlow = mutableQueryParams.asStateFlow() + + val novelPagingData: Flow> = + queryParams + .flatMapLatest { params -> + getUserNovelUseCase( + userId = params.userId, + lastUserNovelId = params.lastUserNovelId, + size = params.size, + sortType = params.sortType, + isInterest = params.isInterest, + readStatuses = params.readStatuses, + attractivePoints = params.attractivePoints, + novelRating = params.novelRating, + query = params.query, + ) + }.cachedIn(viewModelScope) + + private val _uiState = MutableStateFlow(LibraryUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + fun updateViewType() { + _uiState.update { + it.copy(isGrid = !it.isGrid) + } + } + + fun onSortClick(selected: SortTypeUiModel) { + val newSortType = when (selected.sortType) { + SortType.NEWEST -> SortType.OLDEST + SortType.OLDEST -> SortType.NEWEST + } + mutableQueryParams.update { it.copy(sortType = newSortType) } + _uiState.update { it.copy(selectedSortType = SortTypeUiModel.from(newSortType)) } + } + + fun onFilterClick(type: LibraryFilterType) { + // TODO: 바텀시트 연결 예정 + } + + fun onItemClick(item: LibraryListItemModel) { + // TODO: 상세화면 이동 + } + + fun navigateToExplore() { + // TODO: 탐색화면 이동 + } } + +data class LibraryQueryParams( + val userId: Long = 184, + val lastUserNovelId: Long = 0, + val size: Int = 60, + val sortType: SortType = SortType.NEWEST, + val isInterest: Boolean? = null, + val readStatuses: List? = null, + val attractivePoints: List? = null, + val novelRating: Float? = null, + val query: String? = null, +) From 04ac6cb813b64889780eb0764679c2a33e426a61 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Sun, 6 Jul 2025 12:10:11 +0900 Subject: [PATCH 45/53] =?UTF-8?q?feat:=20ux=EB=9D=BC=EC=9D=B4=ED=8C=85=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 --- feature/library/src/main/res/values/string.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/library/src/main/res/values/string.xml b/feature/library/src/main/res/values/string.xml index 194848d17..8adfec635 100644 --- a/feature/library/src/main/res/values/string.xml +++ b/feature/library/src/main/res/values/string.xml @@ -10,7 +10,7 @@ %1$.1f 전체 별점 정렬 - 보관함이 비어있어요 + 서재가 비어있어요 웹소설 찾으러 가기 서재 From 6654a9000c2074b5f679f5cb4fb582b7a3ce4885 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Mon, 7 Jul 2025 09:27:04 +0900 Subject: [PATCH 46/53] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/LibraryViewModel.kt | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt index f4ccc2a0b..c129b15f1 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt @@ -7,11 +7,10 @@ import androidx.paging.cachedIn import com.into.websoso.data.library.model.NovelEntity import com.into.websoso.domain.library.GetUserNovelUseCase import com.into.websoso.domain.library.model.SortType -import com.into.websoso.feature.library.model.LibraryFilterType -import com.into.websoso.feature.library.model.LibraryListItemModel import com.into.websoso.feature.library.model.LibraryUiState import com.into.websoso.feature.library.model.SortTypeUiModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,9 +25,9 @@ class LibraryViewModel constructor( private val getUserNovelUseCase: GetUserNovelUseCase, ) : ViewModel() { - private val mutableQueryParams = MutableStateFlow(LibraryQueryParams()) - private val queryParams: StateFlow = mutableQueryParams.asStateFlow() + private val queryParams = MutableStateFlow(LibraryQueryParams()) + @OptIn(ExperimentalCoroutinesApi::class) val novelPagingData: Flow> = queryParams .flatMapLatest { params -> @@ -54,26 +53,14 @@ class LibraryViewModel } } - fun onSortClick(selected: SortTypeUiModel) { + fun updateSortType(selected: SortTypeUiModel) { val newSortType = when (selected.sortType) { SortType.NEWEST -> SortType.OLDEST SortType.OLDEST -> SortType.NEWEST } - mutableQueryParams.update { it.copy(sortType = newSortType) } + queryParams.update { it.copy(sortType = newSortType) } _uiState.update { it.copy(selectedSortType = SortTypeUiModel.from(newSortType)) } } - - fun onFilterClick(type: LibraryFilterType) { - // TODO: 바텀시트 연결 예정 - } - - fun onItemClick(item: LibraryListItemModel) { - // TODO: 상세화면 이동 - } - - fun navigateToExplore() { - // TODO: 탐색화면 이동 - } } data class LibraryQueryParams( From 92996660ec8bfe0b5f11ad3dc82f5acb9efc4fb9 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Mon, 7 Jul 2025 09:31:57 +0900 Subject: [PATCH 47/53] =?UTF-8?q?refactor:=20=EA=B0=80=EC=8B=9C=EC=84=B1?= =?UTF-8?q?=20=EC=A0=9C=EC=96=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibraryEmptyView.kt | 2 +- .../library/component/LibraryGridList.kt | 2 +- .../library/component/LibraryGridListItem.kt | 17 +++++++++-------- .../feature/library/component/LibraryList.kt | 2 +- .../library/component/LibraryListItem.kt | 2 +- .../library/component/LibrayFilterTopBar.kt | 10 +++++----- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt index 6d1e9381b..53c4cc090 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryEmptyView.kt @@ -30,7 +30,7 @@ import com.into.websoso.feature.library.R.string.library_empty import com.into.websoso.feature.library.R.string.library_go_to_explore @Composable -fun LibraryEmptyView(onExploreClick: () -> Unit = {}) { +internal fun LibraryEmptyView(onExploreClick: () -> Unit = {}) { Box( modifier = Modifier .fillMaxSize() diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt index aad1f3ca8..9be9d962c 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridList.kt @@ -17,7 +17,7 @@ import androidx.paging.compose.LazyPagingItems import com.into.websoso.feature.library.model.LibraryListItemModel @Composable -fun LibraryGridList( +internal fun LibraryGridList( novels: LazyPagingItems, gridState: LazyGridState, modifier: Modifier = Modifier, diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt index 863389a9a..ec0fdd02e 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage +import com.into.websoso.core.designsystem.component.NetworkImage import com.into.websoso.core.designsystem.theme.Black import com.into.websoso.core.designsystem.theme.Gray200 import com.into.websoso.core.designsystem.theme.WebsosoTheme @@ -38,7 +38,6 @@ import com.into.websoso.core.resource.R.drawable.ic_library_null_star import com.into.websoso.core.resource.R.drawable.ic_storage_star import com.into.websoso.feature.library.model.LibraryListItemModel import com.into.websoso.feature.library.model.ReadStatusUiModel -import com.into.websoso.feature.library.util.formatDateRange private const val GRID_COLUMN_COUNT = 3 private val ITEM_SPACING = 6.dp @@ -53,7 +52,7 @@ private enum class RatingStarType { } @Composable -fun NovelGridListItem( +internal fun NovelGridListItem( item: LibraryListItemModel, modifier: Modifier = Modifier, onItemClick: () -> Unit = {}, @@ -84,7 +83,7 @@ fun NovelGridListItem( NovelRatingStar(rating = it) } - formatDateRange(item.startDate, item.endDate)?.let { + item.formattedDateRange?.let { Text( text = it, style = WebsosoTheme.typography.label2, @@ -104,8 +103,8 @@ private fun NovelGridThumbnail( .size(width = size.width, height = size.height) .clip(RoundedCornerShape(8.dp)), ) { - AsyncImage( - model = item.novelImage, + NetworkImage( + imageUrl = item.novelImage, contentDescription = item.title, contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize(), @@ -146,7 +145,8 @@ private fun ReadStatusBadge( .background( color = readStatus.backgroundColor, shape = RoundedCornerShape(4.dp), - ).padding(horizontal = 8.dp, vertical = 4.dp), + ) + .padding(horizontal = 8.dp, vertical = 4.dp), ) } @@ -157,7 +157,8 @@ private fun rememberGridItemSize(): GridItemSize { return remember(screenWidth) { with(density) { - val totalSpacingPx = (ITEM_SPACING * (GRID_COLUMN_COUNT - 1) + HORIZONTAL_PADDING * 2).toPx() + val totalSpacingPx = + (ITEM_SPACING * (GRID_COLUMN_COUNT - 1) + HORIZONTAL_PADDING * 2).toPx() val itemWidthPx = ((screenWidth.dp.toPx() - totalSpacingPx) / GRID_COLUMN_COUNT) val itemHeightPx = itemWidthPx * (IMAGE_ASPECT_HEIGHT / IMAGE_ASPECT_WIDTH) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt index a022f3c50..9c97e8250 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryList.kt @@ -12,7 +12,7 @@ import androidx.paging.compose.LazyPagingItems import com.into.websoso.feature.library.model.LibraryListItemModel @Composable -fun LibraryList( +internal fun LibraryList( novels: LazyPagingItems, listState: LazyListState, onItemClick: (LibraryListItemModel) -> Unit = {}, diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt index af8fee32e..27676e6ac 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt @@ -69,7 +69,7 @@ private const val THUMBNAIL_HEIGHT_RATIO = 80f / 360f private const val FEED_CARD_WIDTH_RATIO = 0.8611f @Composable -fun LibraryListItem( +internal fun LibraryListItem( item: LibraryListItemModel, modifier: Modifier = Modifier, onClick: () -> Unit = {}, diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt index 463d5240c..b71fde290 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt @@ -51,12 +51,12 @@ import com.into.websoso.feature.library.model.SortTypeUiModel import com.into.websoso.feature.library.util.buildFilterLabel @Composable -fun LibraryFilterTopBar( +internal fun LibraryFilterTopBar( libraryFilterUiState: LibraryFilterUiState, totalCount: Int, onFilterClick: (LibraryFilterType) -> Unit, selectedSortType: SortTypeUiModel, - onSortClick: () -> Unit, + onSortClick: (SortTypeUiModel) -> Unit, isGrid: Boolean, onToggleViewType: () -> Unit, modifier: Modifier = Modifier, @@ -179,7 +179,7 @@ private fun NovelFilterStatusBar( totalCount: Int, selectedSortType: SortTypeUiModel, isGrid: Boolean, - onSortClick: () -> Unit, + onSortClick: (SortTypeUiModel) -> Unit, onToggleViewType: () -> Unit, ) { Row( @@ -196,7 +196,7 @@ private fun NovelFilterStatusBar( Row(verticalAlignment = Alignment.CenterVertically) { SortTypeSelector( selectedSortType = selectedSortType, - onClick = { onSortClick() }, + onClick = { onSortClick(SortTypeUiModel.NEWEST) }, ) IconButton(onClick = onToggleViewType) { @@ -213,7 +213,7 @@ private fun NovelFilterStatusBar( } @Composable -fun SortTypeSelector( +private fun SortTypeSelector( selectedSortType: SortTypeUiModel, onClick: () -> Unit, modifier: Modifier = Modifier, From bb9630dd4f7f03d9282c742f6749d042411f0c8c Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Mon, 7 Jul 2025 09:33:15 +0900 Subject: [PATCH 48/53] =?UTF-8?q?refactor:=20=EB=A3=A8=ED=8A=B8=20Composab?= =?UTF-8?q?le=20=ED=95=A8=EC=88=98=20=EB=8F=84=EC=9E=85=ED=95=98=EC=97=AC?= =?UTF-8?q?=20stateful=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/main/library/LibraryFragment.kt | 3 +- .../websoso/feature/library/LibraryScreen.kt | 74 ++++++++++++------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt b/app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt index 5330e0ba4..818e55aa6 100644 --- a/app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt +++ b/app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import com.into.websoso.R import com.into.websoso.core.designsystem.theme.WebsosoTheme +import com.into.websoso.feature.library.LibraryRoute import com.into.websoso.feature.library.LibraryScreen import dagger.hilt.android.AndroidEntryPoint @@ -25,7 +26,7 @@ class LibraryFragment : Fragment() { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { WebsosoTheme { - LibraryScreen() + LibraryRoute() } } } diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt index 6353fb0fb..c7e002e66 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt @@ -10,12 +10,13 @@ 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.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -24,7 +25,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.map import com.into.websoso.core.designsystem.theme.Black @@ -36,54 +39,75 @@ import com.into.websoso.feature.library.component.LibraryFilterTopBar import com.into.websoso.feature.library.component.LibraryGridList import com.into.websoso.feature.library.component.LibraryList import com.into.websoso.feature.library.mapper.toUiModel +import com.into.websoso.feature.library.model.LibraryFilterType +import com.into.websoso.feature.library.model.LibraryListItemModel +import com.into.websoso.feature.library.model.LibraryUiState +import com.into.websoso.feature.library.model.SortTypeUiModel import kotlinx.coroutines.flow.map @Composable -fun LibraryScreen(libraryViewModel: LibraryViewModel = hiltViewModel()) { - val uiState by libraryViewModel.uiState.collectAsState() - - val isGrid = uiState.isGrid - val filterState = uiState.filterUiState - val sortType = uiState.selectedSortType +fun LibraryRoute(libraryViewModel: LibraryViewModel = hiltViewModel()) { + val uiState by libraryViewModel.uiState.collectAsStateWithLifecycle() val pagingItems = libraryViewModel.novelPagingData - .map { pagingData -> pagingData.map { it.toUiModel() } } + .map { it.map { novel -> novel.toUiModel() } } .collectAsLazyPagingItems() val listState = rememberLazyListState() val gridState = rememberLazyGridState() + LibraryScreen( + uiState = uiState, + pagingItems = pagingItems, + listState = listState, + gridState = gridState, + onFilterClick = { /* TODO */ }, + onSortClick = { libraryViewModel.updateSortType(it) }, + onToggleViewType = { libraryViewModel.updateViewType() }, + onItemClick = { /* TODO */ }, + onSearchClick = { /* TODO */ }, + onExploreClick = { /* TODO */ }, + ) +} + +@Composable +fun LibraryScreen( + uiState: LibraryUiState, + pagingItems: LazyPagingItems, + listState: LazyListState, + gridState: LazyGridState, + onFilterClick: (LibraryFilterType) -> Unit, + onSortClick: (SortTypeUiModel) -> Unit, + onToggleViewType: () -> Unit, + onItemClick: (LibraryListItemModel) -> Unit, + onSearchClick: () -> Unit, + onExploreClick: () -> Unit, +) { Column(modifier = Modifier.fillMaxSize()) { - LibraryTopBar( - onSearchClick = { - // TODO: 검색 화면으로 이동 - }, - ) + LibraryTopBar(onSearchClick = onSearchClick) LibraryFilterTopBar( - libraryFilterUiState = filterState, + libraryFilterUiState = uiState.filterUiState, totalCount = pagingItems.itemCount, - selectedSortType = sortType, - isGrid = isGrid, - onFilterClick = { type -> libraryViewModel.onFilterClick(type) }, - onSortClick = { libraryViewModel.onSortClick(sortType) }, - onToggleViewType = { libraryViewModel.updateViewType() }, + selectedSortType = uiState.selectedSortType, + isGrid = uiState.isGrid, + onFilterClick = onFilterClick, + onSortClick = onSortClick, + onToggleViewType = onToggleViewType, ) Spacer(modifier = Modifier.height(4.dp)) when { pagingItems.itemCount == 0 && pagingItems.loadState.refresh !is LoadState.Loading -> { - LibraryEmptyView( - onExploreClick = { libraryViewModel.navigateToExplore() }, - ) + LibraryEmptyView(onExploreClick = onExploreClick) } - isGrid -> { + uiState.isGrid -> { LibraryGridList( novels = pagingItems, gridState = gridState, - onItemClick = { libraryViewModel.onItemClick(it) }, + onItemClick = onItemClick, ) } @@ -91,7 +115,7 @@ fun LibraryScreen(libraryViewModel: LibraryViewModel = hiltViewModel()) { LibraryList( novels = pagingItems, listState = listState, - onItemClick = { libraryViewModel.onItemClick(it) }, + onItemClick = onItemClick, ) } } From 2b393c9884c59041f2d0789b14884aa608b7d76c Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Mon, 7 Jul 2025 09:33:32 +0900 Subject: [PATCH 49/53] =?UTF-8?q?refactor:=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=ED=8F=AC=EB=A9=A7=ED=8C=85=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../into/websoso/feature/library/model/LibraryListItemModel.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt index 160141435..e31aac59c 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt @@ -1,5 +1,7 @@ package com.into.websoso.feature.library.model +import com.into.websoso.feature.library.util.formatDateRange + data class LibraryListItemModel( val novelId: Long, val title: String, @@ -13,4 +15,5 @@ data class LibraryListItemModel( val keywords: List, val myFeeds: List, val isInterest: Boolean, + val formattedDateRange: String? = formatDateRange(startDate, endDate), ) From 7267f32300200322985497484f98a581b95a63da Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Mon, 7 Jul 2025 13:26:05 +0900 Subject: [PATCH 50/53] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=81=AC=EB=A6=B0?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20=EA=B0=80=EC=8B=9C=EC=84=B1=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 --- .../java/com/into/websoso/ui/main/library/LibraryFragment.kt | 3 +-- .../java/com/into/websoso/feature/library/LibraryScreen.kt | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt b/app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt index 818e55aa6..5330e0ba4 100644 --- a/app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt +++ b/app/src/main/java/com/into/websoso/ui/main/library/LibraryFragment.kt @@ -9,7 +9,6 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import com.into.websoso.R import com.into.websoso.core.designsystem.theme.WebsosoTheme -import com.into.websoso.feature.library.LibraryRoute import com.into.websoso.feature.library.LibraryScreen import dagger.hilt.android.AndroidEntryPoint @@ -26,7 +25,7 @@ class LibraryFragment : Fragment() { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { WebsosoTheme { - LibraryRoute() + LibraryScreen() } } } diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt index c7e002e66..ea11fe456 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryScreen.kt @@ -46,7 +46,7 @@ import com.into.websoso.feature.library.model.SortTypeUiModel import kotlinx.coroutines.flow.map @Composable -fun LibraryRoute(libraryViewModel: LibraryViewModel = hiltViewModel()) { +fun LibraryScreen(libraryViewModel: LibraryViewModel = hiltViewModel()) { val uiState by libraryViewModel.uiState.collectAsStateWithLifecycle() val pagingItems = libraryViewModel.novelPagingData @@ -71,7 +71,7 @@ fun LibraryRoute(libraryViewModel: LibraryViewModel = hiltViewModel()) { } @Composable -fun LibraryScreen( +private fun LibraryScreen( uiState: LibraryUiState, pagingItems: LazyPagingItems, listState: LazyListState, From 68c14f8598c4694130fd77dead1d5a12925b73bd Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Mon, 7 Jul 2025 13:39:16 +0900 Subject: [PATCH 51/53] =?UTF-8?q?refactor:=20=EB=82=A0=EC=A7=9C=ED=8F=AC?= =?UTF-8?q?=EB=A9=A7=ED=8C=85=ED=95=A8=EC=88=98=20=EC=BD=94=EC=96=B4?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/common/extensions}/DateFormatter.kt | 2 +- .../feature/library/component/LibrayFilterTopBar.kt | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) rename {feature/library/src/main/java/com/into/websoso/feature/library/util => core/common/src/main/java/com/into/websoso/core/common/extensions}/DateFormatter.kt (94%) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/util/DateFormatter.kt b/core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt similarity index 94% rename from feature/library/src/main/java/com/into/websoso/feature/library/util/DateFormatter.kt rename to core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt index 15a3cb064..2b623f588 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/util/DateFormatter.kt +++ b/core/common/src/main/java/com/into/websoso/core/common/extensions/DateFormatter.kt @@ -1,4 +1,4 @@ -package com.into.websoso.feature.library.util +package com.into.websoso.core.common.extensions import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt index b71fde290..fbb3fe801 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibrayFilterTopBar.kt @@ -40,15 +40,12 @@ import com.into.websoso.core.resource.R.drawable.ic_library_drop_down_fill import com.into.websoso.core.resource.R.drawable.ic_library_grid import com.into.websoso.core.resource.R.drawable.ic_library_list import com.into.websoso.core.resource.R.drawable.ic_library_sort -import com.into.websoso.feature.library.R.string.library_attractive_point import com.into.websoso.feature.library.R.string.library_interesting import com.into.websoso.feature.library.R.string.library_novel_count import com.into.websoso.feature.library.R.string.library_rating -import com.into.websoso.feature.library.R.string.library_read_status import com.into.websoso.feature.library.model.LibraryFilterType import com.into.websoso.feature.library.model.LibraryFilterUiState import com.into.websoso.feature.library.model.SortTypeUiModel -import com.into.websoso.feature.library.util.buildFilterLabel @Composable internal fun LibraryFilterTopBar( @@ -109,10 +106,7 @@ private fun NovelFilterChipSection( ) NovelFilterChip( - text = buildFilterLabel( - stringResource(id = library_read_status), - libraryFilterUiState.readStatusLabel, - ), + text = libraryFilterUiState.readStatusLabelText, isSelected = libraryFilterUiState.readStatusSelected, onClick = { onFilterClick(LibraryFilterType.ReadStatus) }, ) @@ -124,10 +118,7 @@ private fun NovelFilterChipSection( ) NovelFilterChip( - text = buildFilterLabel( - stringResource(id = library_attractive_point), - libraryFilterUiState.attractivePointLabel, - ), + text = libraryFilterUiState.attractivePointLabelText, isSelected = libraryFilterUiState.attractivePointSelected, onClick = { onFilterClick(LibraryFilterType.AttractivePoint) }, ) From 94b48319d6709257ac146f7cbdb242120fbd6687 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Mon, 7 Jul 2025 13:39:40 +0900 Subject: [PATCH 52/53] =?UTF-8?q?refactor:=20=EB=9D=BC=EB=B2=A8=ED=8F=AC?= =?UTF-8?q?=EB=A9=A7=ED=8C=85=EC=9D=84=20ui=EB=AA=A8=EB=8D=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/library/component/LibraryListItem.kt | 2 +- .../feature/library/model/LibraryFilterUiState.kt | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt index 27676e6ac..f2ed2becc 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryListItem.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import coil.compose.AsyncImage +import com.into.websoso.core.common.extensions.formatDateRange import com.into.websoso.core.designsystem.theme.Black import com.into.websoso.core.designsystem.theme.Gray200 import com.into.websoso.core.designsystem.theme.Gray300 @@ -62,7 +63,6 @@ import com.into.websoso.feature.library.R.string.library_total_rating import com.into.websoso.feature.library.model.AttractivePointUiModel import com.into.websoso.feature.library.model.LibraryListItemModel import com.into.websoso.feature.library.model.ReadStatusUiModel -import com.into.websoso.feature.library.util.formatDateRange private const val THUMBNAIL_WIDTH_RATIO = 60f / 360f private const val THUMBNAIL_HEIGHT_RATIO = 80f / 360f diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiState.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiState.kt index 459376f2e..b67ff571c 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiState.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiState.kt @@ -8,4 +8,17 @@ data class LibraryFilterUiState( val ratingSelected: Boolean = false, val attractivePointLabel: List = emptyList(), val attractivePointSelected: Boolean = false, -) +) { + val readStatusLabelText: String = buildLabel(readStatusLabel, "읽기 상태") + val attractivePointLabelText: String = buildLabel(attractivePointLabel, "매력 포인트") + + private fun buildLabel( + values: List, + labelTitle: String, + ): String = + when { + values.isEmpty() -> labelTitle + values.size == 1 -> values.first() + else -> "${values.first()} 외 ${values.size - 1}개" + } +} From 71bb035bac7f6740f502476b70de913d4f522260 Mon Sep 17 00:00:00 2001 From: yeonjeen Date: Mon, 7 Jul 2025 13:40:05 +0900 Subject: [PATCH 53/53] =?UTF-8?q?refactor:=20=ED=8F=89=EC=A0=90=20?= =?UTF-8?q?=EB=B3=84UI=20=EB=A1=9C=EC=A7=81=EC=9D=84=20UI=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../library/component/LibraryGridListItem.kt | 32 ++++--------------- .../library/model/LibraryListItemModel.kt | 26 +++++++++++++-- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt index ec0fdd02e..5f4927011 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/component/LibraryGridListItem.kt @@ -37,6 +37,7 @@ import com.into.websoso.core.resource.R.drawable.ic_library_interesting import com.into.websoso.core.resource.R.drawable.ic_library_null_star import com.into.websoso.core.resource.R.drawable.ic_storage_star import com.into.websoso.feature.library.model.LibraryListItemModel +import com.into.websoso.feature.library.model.RatingStarType import com.into.websoso.feature.library.model.ReadStatusUiModel private const val GRID_COLUMN_COUNT = 3 @@ -45,12 +46,6 @@ private val HORIZONTAL_PADDING = 20.dp private const val IMAGE_ASPECT_WIDTH = 102.67f private const val IMAGE_ASPECT_HEIGHT = 160f -private enum class RatingStarType { - FULL, - HALF, - EMPTY, -} - @Composable internal fun NovelGridListItem( item: LibraryListItemModel, @@ -79,8 +74,8 @@ internal fun NovelGridListItem( overflow = TextOverflow.Ellipsis, ) - item.userNovelRating?.let { - NovelRatingStar(rating = it) + if (item.ratingStars.isNotEmpty()) { + NovelRatingStar(stars = item.ratingStars) } item.formattedDateRange?.let { @@ -145,8 +140,7 @@ private fun ReadStatusBadge( .background( color = readStatus.backgroundColor, shape = RoundedCornerShape(4.dp), - ) - .padding(horizontal = 8.dp, vertical = 4.dp), + ).padding(horizontal = 8.dp, vertical = 4.dp), ) } @@ -168,15 +162,15 @@ private fun rememberGridItemSize(): GridItemSize { } @Composable -private fun NovelRatingStar( - rating: Float, +fun NovelRatingStar( + stars: List, modifier: Modifier = Modifier, ) { Row( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(2.dp), ) { - calculateRatingStars(rating).forEach { starType -> + stars.forEach { starType -> Image( imageVector = ratingStarIcon(starType), contentDescription = null, @@ -196,18 +190,6 @@ private fun ratingStarIcon(starType: RatingStarType): ImageVector { return ImageVector.vectorResource(id = resId) } -private fun calculateRatingStars(rating: Float): List { - val fullStar = rating.toInt() - val halfStar = (rating - fullStar) >= 0.5f - val emptyStar = 5 - fullStar - if (halfStar) 1 else 0 - - return buildList { - repeat(fullStar) { add(RatingStarType.FULL) } - if (halfStar) add(RatingStarType.HALF) - repeat(emptyStar) { add(RatingStarType.EMPTY) } - } -} - private data class GridItemSize( val width: Dp, val height: Dp, diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt index e31aac59c..7f923ac2e 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt @@ -1,6 +1,6 @@ package com.into.websoso.feature.library.model -import com.into.websoso.feature.library.util.formatDateRange +import com.into.websoso.core.common.extensions.formatDateRange data class LibraryListItemModel( val novelId: Long, @@ -16,4 +16,26 @@ data class LibraryListItemModel( val myFeeds: List, val isInterest: Boolean, val formattedDateRange: String? = formatDateRange(startDate, endDate), -) +) { + val ratingStars: List = calculateRatingStars(userNovelRating) + + private fun calculateRatingStars(rating: Float?): List { + if (rating == null) return emptyList() + + val fullStar = rating.toInt() + val halfStar = (rating - fullStar) >= 0.5f + val emptyStar = 5 - fullStar - if (halfStar) 1 else 0 + + return buildList { + repeat(fullStar) { add(RatingStarType.FULL) } + if (halfStar) add(RatingStarType.HALF) + repeat(emptyStar) { add(RatingStarType.EMPTY) } + } + } +} + +enum class RatingStarType { + FULL, + HALF, + EMPTY, +}