diff --git a/app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt b/app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt index 34c2bb2cc..c2b15bf72 100644 --- a/app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt +++ b/app/src/main/java/com/into/websoso/ui/splash/SplashViewModel.kt @@ -10,6 +10,7 @@ import com.into.websoso.ui.splash.UiEffect.NavigateToMain import com.into.websoso.ui.splash.UiEffect.ShowDialog import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch @@ -51,6 +52,8 @@ class SplashViewModel } private suspend fun handleAutoLogin() { + delay(1000) + if (shouldRefresh()) { _uiEffect.send(NavigateToLogin) return diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index fc219f8d3..fbafb361f 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -9,18 +9,15 @@ android { } dependencies { - // 데이터베이스 관련 라이브러리 + // 데이터 레이어 의존성 + implementation(projects.data.library) + + // 페이징 관련 의존성 + implementation(libs.paging.runtime) + + // 데이터베이스 관련 의존성 + implementation(libs.room.paging) implementation(libs.room.ktx) implementation(libs.room.runtime) kapt(libs.room.compiler) - - // 페이징3 - val paging_version = "3.3.6" - implementation("androidx.room:room-paging:2.5.1") // Room 최신 버전에 맞게 버전 확인 - - - implementation("androidx.paging:paging-runtime:$paging_version") - - // alternatively - without Android dependencies for tests - implementation("androidx.paging:paging-common:$paging_version") } diff --git a/core/database/src/main/java/com/into/websoso/core/database/Converters.kt b/core/database/src/main/java/com/into/websoso/core/database/Converters.kt index c3c050091..c4b0f8e80 100644 --- a/core/database/src/main/java/com/into/websoso/core/database/Converters.kt +++ b/core/database/src/main/java/com/into/websoso/core/database/Converters.kt @@ -2,7 +2,7 @@ package com.into.websoso.core.database import androidx.room.TypeConverter -class Converters { +internal class Converters { @TypeConverter fun fromString(value: String): List = if (value.isEmpty()) emptyList() else value.split(",") diff --git a/core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt b/core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt index f129c2d50..7da90bf67 100644 --- a/core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt +++ b/core/database/src/main/java/com/into/websoso/core/database/WebsosoDatabase.kt @@ -1,45 +1,17 @@ package com.into.websoso.core.database -import android.content.Context import androidx.room.Database -import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters -import com.into.websoso.core.database.dao.FilteredNovelDao -import com.into.websoso.core.database.dao.NovelDao -import com.into.websoso.core.database.entity.InDatabaseFilteredNovelEntity -import com.into.websoso.core.database.entity.InDatabaseNovelEntity -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton +import com.into.websoso.core.database.datasource.library.dao.NovelDao +import com.into.websoso.core.database.datasource.library.entity.InDatabaseNovelEntity @Database( - entities = [InDatabaseNovelEntity::class, InDatabaseFilteredNovelEntity::class], - version = 4, + entities = [InDatabaseNovelEntity::class], + version = 5, exportSchema = false, ) @TypeConverters(Converters::class) internal abstract class WebsosoDatabase : RoomDatabase() { internal abstract fun novelDao(): NovelDao - - internal abstract fun filteredNovelDao(): FilteredNovelDao -} - -@Module -@InstallIn(SingletonComponent::class) -internal object DatabaseModule { - @Provides - @Singleton - internal fun provideDatabase( - @ApplicationContext context: Context, - ): WebsosoDatabase = - Room - .databaseBuilder( - context, - WebsosoDatabase::class.java, - "websoso.db", - ).build() } diff --git a/core/database/src/main/java/com/into/websoso/core/database/dao/FilteredNovelDao.kt b/core/database/src/main/java/com/into/websoso/core/database/dao/FilteredNovelDao.kt deleted file mode 100644 index 50aa44b6a..000000000 --- a/core/database/src/main/java/com/into/websoso/core/database/dao/FilteredNovelDao.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.into.websoso.core.database.dao - -import androidx.paging.PagingSource -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import com.into.websoso.core.database.WebsosoDatabase -import com.into.websoso.core.database.entity.InDatabaseFilteredNovelEntity -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Dao -interface FilteredNovelDao { - @Query("SELECT * FROM filtered_novels ORDER BY sortIndex ASC") - fun selectAllNovels(): PagingSource - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertFilteredNovels(novels: List) - - @Query("DELETE FROM filtered_novels") - suspend fun clearAll() - - @Query("SELECT COUNT(*) FROM filtered_novels") - suspend fun selectNovelsCount(): Int -} - -@Module -@InstallIn(SingletonComponent::class) -internal object FilteredNovelDaoModule { - @Provides - @Singleton - internal fun provideFilteredNovelDao(database: WebsosoDatabase): FilteredNovelDao = database.filteredNovelDao() -} diff --git a/core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt new file mode 100644 index 000000000..fbe028472 --- /dev/null +++ b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/DefaultLibraryLocalDataSource.kt @@ -0,0 +1,45 @@ +package com.into.websoso.core.database.datasource.library + +import androidx.paging.PagingSource +import com.into.websoso.core.database.datasource.library.dao.NovelDao +import com.into.websoso.core.database.datasource.library.entity.InDatabaseNovelEntity +import com.into.websoso.core.database.datasource.library.mapper.toData +import com.into.websoso.core.database.datasource.library.mapper.toNovelDatabase +import com.into.websoso.core.database.datasource.library.paging.mapValue +import com.into.websoso.data.library.datasource.LibraryLocalDataSource +import com.into.websoso.data.library.model.NovelEntity +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Inject +import javax.inject.Singleton + +internal class DefaultLibraryLocalDataSource + @Inject + constructor( + private val novelDao: NovelDao, + ) : LibraryLocalDataSource { + override suspend fun insertNovels(novels: List) { + val offset = novelDao.selectNovelsCount() + novelDao.insertNovels( + novels.mapIndexed { index, novelEntity -> + novelEntity.toNovelDatabase(offset + index) + }, + ) + } + + override fun selectAllNovels(): PagingSource = novelDao.selectAllNovels().mapValue(InDatabaseNovelEntity::toData) + + override suspend fun deleteAllNovels() { + novelDao.deleteAllNovels() + } + } + +@Module +@InstallIn(SingletonComponent::class) +internal interface LibraryDataSourceModule { + @Binds + @Singleton + fun bindLibraryDataSource(defaultLibraryLocalDataSource: DefaultLibraryLocalDataSource): LibraryLocalDataSource +} diff --git a/core/database/src/main/java/com/into/websoso/core/database/dao/NovelDao.kt b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt similarity index 69% rename from core/database/src/main/java/com/into/websoso/core/database/dao/NovelDao.kt rename to core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt index bace81e9d..f3734d707 100644 --- a/core/database/src/main/java/com/into/websoso/core/database/dao/NovelDao.kt +++ b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/dao/NovelDao.kt @@ -1,4 +1,4 @@ -package com.into.websoso.core.database.dao +package com.into.websoso.core.database.datasource.library.dao import androidx.paging.PagingSource import androidx.room.Dao @@ -6,7 +6,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import com.into.websoso.core.database.WebsosoDatabase -import com.into.websoso.core.database.entity.InDatabaseNovelEntity +import com.into.websoso.core.database.datasource.library.entity.InDatabaseNovelEntity import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -14,15 +14,18 @@ import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Dao -interface NovelDao { - @Query("SELECT * FROM novels ORDER BY userNovelId DESC") +internal interface NovelDao { + @Query("SELECT * FROM novels ORDER BY sortIndex ASC") fun selectAllNovels(): PagingSource + @Query("SELECT COUNT(*) FROM novels") + suspend fun selectNovelsCount(): Int + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertNovels(novels: List) @Query("DELETE FROM novels") - suspend fun clearAll() + suspend fun deleteAllNovels() } @Module diff --git a/core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseNovelEntity.kt b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt similarity index 78% rename from core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseNovelEntity.kt rename to core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt index 4c8310d30..144811e7a 100644 --- a/core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseNovelEntity.kt +++ b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/entity/InDatabaseNovelEntity.kt @@ -1,13 +1,14 @@ -package com.into.websoso.core.database.entity +package com.into.websoso.core.database.datasource.library.entity import androidx.room.Entity import androidx.room.PrimaryKey @Entity(tableName = "novels") -data class InDatabaseNovelEntity( +internal data class InDatabaseNovelEntity( @PrimaryKey val userNovelId: Long, val novelId: Long, val title: String, + val sortIndex: Int, val novelImage: String, val novelRating: Float, val readStatus: String, @@ -19,3 +20,4 @@ data class InDatabaseNovelEntity( val keywords: List, val myFeeds: List, ) + diff --git a/core/database/src/main/java/com/into/websoso/core/database/datasource/library/mapper/NovelMapper.kt b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/mapper/NovelMapper.kt new file mode 100644 index 000000000..1c2b46974 --- /dev/null +++ b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/mapper/NovelMapper.kt @@ -0,0 +1,39 @@ +package com.into.websoso.core.database.datasource.library.mapper + +import com.into.websoso.core.database.datasource.library.entity.InDatabaseNovelEntity +import com.into.websoso.data.library.model.NovelEntity + +internal fun NovelEntity.toNovelDatabase(index: Int): InDatabaseNovelEntity = + InDatabaseNovelEntity( + userNovelId = userNovelId, + novelId = novelId, + title = title, + novelImage = novelImage, + novelRating = novelRating, + readStatus = readStatus, + isInterest = isInterest, + userNovelRating = userNovelRating, + attractivePoints = attractivePoints, + startDate = startDate, + endDate = endDate, + keywords = keywords, + myFeeds = myFeeds, + sortIndex = index, + ) + +internal fun InDatabaseNovelEntity.toData(): NovelEntity = + NovelEntity( + userNovelId = userNovelId, + novelId = novelId, + title = title, + novelImage = novelImage, + novelRating = novelRating, + readStatus = readStatus, + isInterest = isInterest, + userNovelRating = userNovelRating, + attractivePoints = attractivePoints, + startDate = startDate, + endDate = endDate, + keywords = keywords, + myFeeds = myFeeds, + ) diff --git a/core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt new file mode 100644 index 000000000..7e7a1ff55 --- /dev/null +++ b/core/database/src/main/java/com/into/websoso/core/database/datasource/library/paging/MapValuePagingSource.kt @@ -0,0 +1,30 @@ +package com.into.websoso.core.database.datasource.library.paging + +import androidx.paging.PagingSource +import androidx.paging.PagingState + +internal fun PagingSource.mapValue(mapper: suspend (From) -> To): PagingSource = + MapValuePagingSource(this, mapper) + +internal class MapValuePagingSource( + private val targetSource: PagingSource, + private val mapper: suspend (From) -> To, +) : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult = + when (val result = targetSource.load(params)) { + is LoadResult.Page -> LoadResult.Page( + data = result.data.map { mapper(it) }, + prevKey = result.prevKey, + nextKey = result.nextKey, + itemsBefore = result.itemsBefore, + itemsAfter = result.itemsAfter, + ) + + is LoadResult.Error -> LoadResult.Error(result.throwable) + is LoadResult.Invalid -> LoadResult.Invalid() + } + + override val jumpingSupported: Boolean get() = targetSource.jumpingSupported + + override fun getRefreshKey(state: PagingState): Key? = null +} diff --git a/core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt b/core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt new file mode 100644 index 000000000..1c383df53 --- /dev/null +++ b/core/database/src/main/java/com/into/websoso/core/database/di/DatabaseModule.kt @@ -0,0 +1,28 @@ +package com.into.websoso.core.database.di + +import android.content.Context +import androidx.room.Room +import com.into.websoso.core.database.WebsosoDatabase +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object DatabaseModule { + @Provides + @Singleton + internal fun provideDatabase( + @ApplicationContext context: Context, + ): WebsosoDatabase = + Room + .databaseBuilder( + context, + WebsosoDatabase::class.java, + "websoso.db", + ).fallbackToDestructiveMigration() + .build() +} diff --git a/core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseFilteredNovelEntity.kt b/core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseFilteredNovelEntity.kt deleted file mode 100644 index de1fd3ee7..000000000 --- a/core/database/src/main/java/com/into/websoso/core/database/entity/InDatabaseFilteredNovelEntity.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.into.websoso.core.database.entity - -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "filtered_novels") -data class InDatabaseFilteredNovelEntity( - @PrimaryKey val userNovelId: Long, - val novelId: Long, - val title: String, - val sortIndex: Int, - val novelImage: String, - val novelRating: Float, - val readStatus: String, - val isInterest: Boolean, - val userNovelRating: Float, - val attractivePoints: List, - val startDate: String, - val endDate: String, - val keywords: List, - val myFeeds: List, -) diff --git a/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt b/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt index 6e34a676c..ff02ad48c 100644 --- a/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt +++ b/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt @@ -42,10 +42,10 @@ internal class DefaultLibraryFilterDataSource } }.distinctUntilChanged() - override suspend fun updateLibraryFilter(params: LibraryFilter) { + override suspend fun updateLibraryFilter(libraryFilter: LibraryFilter) { val encodedJsonString = withContext(dispatcher) { Json.encodeToString( - params.toPreferences(), + libraryFilter.toPreferences(), ) } diff --git a/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt b/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt index c02114a6a..af309d4b3 100644 --- a/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt +++ b/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/model/LibraryFilterPreferences.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable internal data class LibraryFilterPreferences( val sortCriteria: String, val isInterested: Boolean, - val readStatuses: Map, - val attractivePoints: Map, + val readStatuses: List, + val attractivePoints: List, val novelRating: Float, ) diff --git a/data/library/build.gradle.kts b/data/library/build.gradle.kts index 385c3b819..a3ab311cc 100644 --- a/data/library/build.gradle.kts +++ b/data/library/build.gradle.kts @@ -10,13 +10,6 @@ android { dependencies { implementation(projects.core.common) - implementation(projects.core.database) implementation(projects.data.account) - // 페이징3 - val paging_version = "3.3.6" - - implementation("androidx.paging:paging-runtime:$paging_version") - - // alternatively - without Android dependencies for tests - implementation("androidx.paging:paging-common:$paging_version") + implementation(libs.paging.runtime) } diff --git a/data/library/src/main/java/com/into/websoso/data/filter/FilterRepository.kt b/data/library/src/main/java/com/into/websoso/data/filter/FilterRepository.kt index b6b759d9f..acc47497c 100644 --- a/data/library/src/main/java/com/into/websoso/data/filter/FilterRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/filter/FilterRepository.kt @@ -7,8 +7,8 @@ interface FilterRepository { val filterFlow: Flow suspend fun updateFilter( - readStatuses: Map? = null, - attractivePoints: Map? = null, + readStatuses: List? = null, + attractivePoints: List? = null, novelRating: Float? = null, isInterested: Boolean? = null, sortCriteria: String? = null, diff --git a/data/library/src/main/java/com/into/websoso/data/filter/datasource/LibraryFilterLocalDataSource.kt b/data/library/src/main/java/com/into/websoso/data/filter/datasource/LibraryFilterLocalDataSource.kt index cf4a714ae..58da4e35e 100644 --- a/data/library/src/main/java/com/into/websoso/data/filter/datasource/LibraryFilterLocalDataSource.kt +++ b/data/library/src/main/java/com/into/websoso/data/filter/datasource/LibraryFilterLocalDataSource.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow interface LibraryFilterLocalDataSource { val libraryFilterFlow: Flow - suspend fun updateLibraryFilter(params: LibraryFilter) + suspend fun updateLibraryFilter(libraryFilter: LibraryFilter) suspend fun deleteLibraryFilter() } diff --git a/data/library/src/main/java/com/into/websoso/data/filter/model/LibraryFilter.kt b/data/library/src/main/java/com/into/websoso/data/filter/model/LibraryFilter.kt index dbfcbf270..fa49e9da5 100644 --- a/data/library/src/main/java/com/into/websoso/data/filter/model/LibraryFilter.kt +++ b/data/library/src/main/java/com/into/websoso/data/filter/model/LibraryFilter.kt @@ -1,24 +1,13 @@ package com.into.websoso.data.filter.model +const val DEFAULT_SORT_CRITERIA = "RECENT" +const val DEFAULT_IS_INTERESTED = false +const val DEFAULT_NOVEL_RATING = 0.0f + data class LibraryFilter( val sortCriteria: String = DEFAULT_SORT_CRITERIA, val isInterested: Boolean = DEFAULT_IS_INTERESTED, val novelRating: Float = DEFAULT_NOVEL_RATING, - val readStatuses: Map = emptyMap(), - val attractivePoints: Map = emptyMap(), -) { - val readStatusKeys: List = readStatuses.toSelectedKeyList { it } - val attractivePointKeys: List = attractivePoints.toSelectedKeyList { it } - - private fun Map.toSelectedKeyList(toSelectedKeyName: (K) -> String): List = - this - .filterValues { it } - .keys - .map { toSelectedKeyName(it) } - - companion object { - private const val DEFAULT_SORT_CRITERIA = "RECENT" - private const val DEFAULT_IS_INTERESTED = false - private const val DEFAULT_NOVEL_RATING = 0.0f - } -} + val readStatuses: List = emptyList(), + val attractivePoints: List = emptyList(), +) diff --git a/data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt b/data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt index 621daa5e7..cdd2cde6f 100644 --- a/data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt @@ -20,8 +20,8 @@ internal class MyLibraryFilterRepository .distinctUntilChanged() override suspend fun updateFilter( - readStatuses: Map?, - attractivePoints: Map?, + readStatuses: List?, + attractivePoints: List?, novelRating: Float?, isInterested: Boolean?, sortCriteria: String?, @@ -35,6 +35,6 @@ internal class MyLibraryFilterRepository novelRating = novelRating ?: savedFilter.novelRating, ) - myLibraryFilterLocalDataSource.updateLibraryFilter(params = updatedFilter) + myLibraryFilterLocalDataSource.updateLibraryFilter(libraryFilter = updatedFilter) } } diff --git a/data/library/src/main/java/com/into/websoso/data/filter/repository/UserLibraryFilterRepository.kt b/data/library/src/main/java/com/into/websoso/data/filter/repository/UserLibraryFilterRepository.kt index 03f67c513..91115a32c 100644 --- a/data/library/src/main/java/com/into/websoso/data/filter/repository/UserLibraryFilterRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/filter/repository/UserLibraryFilterRepository.kt @@ -16,8 +16,8 @@ internal class UserLibraryFilterRepository override val filterFlow: Flow = _filterFlow.asStateFlow() override suspend fun updateFilter( - readStatuses: Map?, - attractivePoints: Map?, + readStatuses: List?, + attractivePoints: List?, novelRating: Float?, isInterested: Boolean?, sortCriteria: String?, diff --git a/data/library/src/main/java/com/into/websoso/data/library/datasource/FilteredLibraryLocalDataSource.kt b/data/library/src/main/java/com/into/websoso/data/library/datasource/FilteredLibraryLocalDataSource.kt deleted file mode 100644 index 99c57b970..000000000 --- a/data/library/src/main/java/com/into/websoso/data/library/datasource/FilteredLibraryLocalDataSource.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.into.websoso.data.library.datasource - -import androidx.paging.PagingSource -import com.into.websoso.core.database.dao.FilteredNovelDao -import com.into.websoso.core.database.entity.InDatabaseFilteredNovelEntity -import com.into.websoso.data.library.model.NovelEntity -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Inject -import javax.inject.Singleton - -interface FilteredLibraryLocalDataSource { - suspend fun insertNovels(novels: List) - - fun selectAllNovels(): PagingSource - - suspend fun deleteAllNovels() -} - -internal class DefaultFilteredLibraryLocalDataSource - @Inject - constructor( - private val filteredNovelDao: FilteredNovelDao, - ) : FilteredLibraryLocalDataSource { - override suspend fun insertNovels(novels: List) { - val offset = filteredNovelDao.selectNovelsCount() - filteredNovelDao.insertFilteredNovels( - novels.mapIndexed { index, novelEntity -> - novelEntity.toFilteredNovelDatabase(offset + index) - }, - ) - } - - override fun selectAllNovels(): PagingSource = filteredNovelDao.selectAllNovels() - - override suspend fun deleteAllNovels() { - filteredNovelDao.clearAll() - } - } - -@Module -@InstallIn(SingletonComponent::class) -internal interface FilteredLibraryDataSourceModule { - @Binds - @Singleton - fun bindFilteredLibraryDataSource( - defaultFilteredLibraryLocalDataSource: DefaultFilteredLibraryLocalDataSource, - ): FilteredLibraryLocalDataSource -} 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 8c186f548..7284a9601 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,44 +1,12 @@ package com.into.websoso.data.library.datasource import androidx.paging.PagingSource -import com.into.websoso.core.database.dao.NovelDao -import com.into.websoso.core.database.entity.InDatabaseNovelEntity import com.into.websoso.data.library.model.NovelEntity -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Inject -import javax.inject.Singleton interface LibraryLocalDataSource { suspend fun insertNovels(novels: List) - fun selectAllNovels(): PagingSource + fun selectAllNovels(): PagingSource suspend fun deleteAllNovels() } - -internal class DefaultLibraryDataSource - @Inject - constructor( - private val novelDao: NovelDao, - ) : LibraryLocalDataSource { - override suspend fun insertNovels(novels: List) { - novelDao.insertNovels(novels.map(NovelEntity::toNovelDatabase)) - } - - override fun selectAllNovels(): PagingSource = novelDao.selectAllNovels() - - override suspend fun deleteAllNovels() { - novelDao.clearAll() - } - } - -@Module -@InstallIn(SingletonComponent::class) -internal interface LibraryDataSourceModule { - @Binds - @Singleton - fun bindLibraryLocalDataSource(defaultLibraryDataSource: DefaultLibraryDataSource): LibraryLocalDataSource -} diff --git a/data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt b/data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt index 8d6c54b92..060e1b7e3 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/model/NovelEntity.kt @@ -1,8 +1,5 @@ package com.into.websoso.data.library.model -import com.into.websoso.core.database.entity.InDatabaseFilteredNovelEntity -import com.into.websoso.core.database.entity.InDatabaseNovelEntity - data class UserNovelsEntity( val isLoadable: Boolean, val userNovelCount: Long, @@ -23,73 +20,4 @@ data class NovelEntity( val endDate: String, val keywords: List, val myFeeds: List, -) { - internal fun toNovelDatabase(): InDatabaseNovelEntity = - InDatabaseNovelEntity( - userNovelId = userNovelId, - novelId = novelId, - title = title, - novelImage = novelImage, - novelRating = novelRating, - readStatus = readStatus, - isInterest = isInterest, - userNovelRating = userNovelRating, - attractivePoints = attractivePoints, - startDate = startDate, - endDate = endDate, - keywords = keywords, - myFeeds = myFeeds, - ) - - internal fun toFilteredNovelDatabase(index: Int): InDatabaseFilteredNovelEntity = - InDatabaseFilteredNovelEntity( - userNovelId = userNovelId, - novelId = novelId, - title = title, - novelImage = novelImage, - novelRating = novelRating, - readStatus = readStatus, - isInterest = isInterest, - userNovelRating = userNovelRating, - attractivePoints = attractivePoints, - startDate = startDate, - endDate = endDate, - keywords = keywords, - myFeeds = myFeeds, - sortIndex = index, - ) -} - -fun InDatabaseNovelEntity.toData(): NovelEntity = - NovelEntity( - userNovelId = userNovelId, - novelId = novelId, - title = title, - novelImage = novelImage, - novelRating = novelRating, - readStatus = readStatus, - isInterest = isInterest, - userNovelRating = userNovelRating, - attractivePoints = attractivePoints, - startDate = startDate, - endDate = endDate, - keywords = keywords, - myFeeds = myFeeds, - ) - -fun InDatabaseFilteredNovelEntity.toData(): NovelEntity = - NovelEntity( - userNovelId = userNovelId, - novelId = novelId, - title = title, - novelImage = novelImage, - novelRating = novelRating, - readStatus = readStatus, - isInterest = isInterest, - userNovelRating = userNovelRating, - attractivePoints = attractivePoints, - startDate = startDate, - endDate = endDate, - keywords = keywords, - myFeeds = myFeeds, - ) +) diff --git a/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt b/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt index f3a5db86b..128587ca5 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt @@ -1,48 +1,36 @@ package com.into.websoso.data.library.paging import androidx.paging.PagingSource +import androidx.paging.PagingSource.LoadResult.Error +import androidx.paging.PagingSource.LoadResult.Page import androidx.paging.PagingState -import com.into.websoso.core.common.extensions.isCloseTo -import com.into.websoso.data.filter.model.LibraryFilter -import com.into.websoso.data.library.datasource.LibraryRemoteDataSource import com.into.websoso.data.library.model.NovelEntity +import com.into.websoso.data.library.model.UserNovelsEntity class LibraryPagingSource( - private val userId: Long, - private val libraryRemoteDataSource: LibraryRemoteDataSource, - private val filterParams: LibraryFilter, + private val getNovels: suspend (lastUserNovelId: Long) -> Result, ) : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { val lastUserNovelId = params.key ?: DEFAULT_LAST_USER_NOVEL_ID - return try { - val response = libraryRemoteDataSource.getUserNovels( - userId = userId, - lastUserNovelId = lastUserNovelId, - size = params.loadSize, - sortCriteria = filterParams.sortCriteria, - isInterest = if (!filterParams.isInterested) null else true, - readStatuses = filterParams.readStatusKeys.ifEmpty { null }, - attractivePoints = filterParams.attractivePointKeys.ifEmpty { null }, - novelRating = if (filterParams.novelRating.isCloseTo(0f)) null else filterParams.novelRating, - query = null, - updatedSince = null, - ) + return getNovels(lastUserNovelId).fold( + onSuccess = { result -> + val nextKey = if (result.isLoadable && result.userNovels.isNotEmpty()) { + result.userNovels.last().userNovelId + } else { + null + } - val nextKey = if (response.isLoadable && response.userNovels.isNotEmpty()) { - response.userNovels.last().userNovelId - } else { - null - } - - LoadResult.Page( - data = response.userNovels, - prevKey = null, - nextKey = nextKey, - ) - } catch (e: Exception) { - LoadResult.Error(e) - } + Page( + data = result.userNovels, + prevKey = null, + nextKey = nextKey, + ) + }, + onFailure = { throwable -> + Error(throwable) + }, + ) } override fun getRefreshKey(state: PagingState): Long? = diff --git a/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt b/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt index df343a219..55be217b9 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt @@ -9,59 +9,38 @@ import androidx.paging.PagingState import androidx.paging.RemoteMediator import androidx.paging.RemoteMediator.MediatorResult.Error import androidx.paging.RemoteMediator.MediatorResult.Success -import com.into.websoso.core.common.extensions.isCloseTo -import com.into.websoso.core.database.entity.InDatabaseFilteredNovelEntity -import com.into.websoso.data.filter.model.LibraryFilter -import com.into.websoso.data.library.datasource.FilteredLibraryLocalDataSource -import com.into.websoso.data.library.datasource.LibraryRemoteDataSource +import com.into.websoso.data.library.model.NovelEntity +import com.into.websoso.data.library.model.UserNovelsEntity @OptIn(ExperimentalPagingApi::class) class LibraryRemoteMediator( - private val userId: Long, - private val libraryRemoteDataSource: LibraryRemoteDataSource, - private val filteredLibraryLocalDataSource: FilteredLibraryLocalDataSource, - private val libraryFilter: LibraryFilter, -) : RemoteMediator() { + private val getNovels: suspend (lastUserNovelId: Long) -> Result, + private val deleteAllNovels: suspend () -> Unit, + private val insertNovels: suspend (List) -> Unit, +) : RemoteMediator() { override suspend fun load( loadType: LoadType, - state: PagingState, + state: PagingState, ): MediatorResult { val lastUserNovelId = when (loadType) { REFRESH -> DEFAULT_LAST_USER_NOVEL_ID - PREPEND -> return Success(endOfPaginationReached = true) - APPEND -> state.lastItemOrNull()?.userNovelId ?: return Success(true) - } + APPEND -> state.lastItemOrNull()?.userNovelId + PREPEND -> null + } ?: return Success(true) - return try { - val response = libraryRemoteDataSource.getUserNovels( - userId = userId, - lastUserNovelId = lastUserNovelId, - size = state.config.pageSize, - sortCriteria = libraryFilter.sortCriteria, - isInterest = if (!libraryFilter.isInterested) null else true, - readStatuses = libraryFilter.readStatusKeys.ifEmpty { null }, - attractivePoints = libraryFilter.attractivePointKeys.ifEmpty { null }, - novelRating = if (libraryFilter.novelRating.isCloseTo(DEFAULT_NOVEL_RATING)) { - null - } else { - libraryFilter.novelRating - }, - query = null, - updatedSince = null, - ) - - if (loadType == REFRESH) filteredLibraryLocalDataSource.deleteAllNovels() - - filteredLibraryLocalDataSource.insertNovels(response.userNovels) - - Success(endOfPaginationReached = !response.isLoadable) - } catch (e: Exception) { - Error(e) - } + return getNovels(lastUserNovelId).fold( + onSuccess = { result -> + if (loadType == REFRESH) deleteAllNovels() + insertNovels(result.userNovels) + Success(endOfPaginationReached = !result.isLoadable) + }, + onFailure = { throwable -> + Error(throwable) + }, + ) } companion object { private const val DEFAULT_LAST_USER_NOVEL_ID = 0L - private const val DEFAULT_NOVEL_RATING = 0f } } diff --git a/data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt b/data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt index b6ac84ef9..6508fc56e 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt @@ -4,21 +4,20 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData -import androidx.paging.map -import com.into.websoso.core.database.entity.InDatabaseFilteredNovelEntity +import com.into.websoso.core.common.extensions.isCloseTo import com.into.websoso.data.account.AccountRepository import com.into.websoso.data.filter.FilterRepository +import com.into.websoso.data.filter.model.DEFAULT_NOVEL_RATING +import com.into.websoso.data.filter.model.LibraryFilter import com.into.websoso.data.library.LibraryRepository import com.into.websoso.data.library.LibraryRepository.Companion.PAGE_SIZE -import com.into.websoso.data.library.datasource.FilteredLibraryLocalDataSource +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 com.into.websoso.data.library.model.toData import com.into.websoso.data.library.paging.LibraryRemoteMediator import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map import javax.inject.Inject internal class MyLibraryRepository @@ -27,7 +26,7 @@ internal class MyLibraryRepository filterRepository: FilterRepository, private val accountRepository: AccountRepository, private val libraryRemoteDataSource: LibraryRemoteDataSource, - private val filteredLibraryLocalDataSource: FilteredLibraryLocalDataSource, + private val libraryLocalDataSource: LibraryLocalDataSource, ) : LibraryRepository { @OptIn(ExperimentalPagingApi::class, ExperimentalCoroutinesApi::class) override val libraryFlow: Flow> = @@ -36,14 +35,33 @@ internal class MyLibraryRepository Pager( config = PagingConfig(pageSize = PAGE_SIZE), remoteMediator = LibraryRemoteMediator( - userId = accountRepository.userId, - libraryRemoteDataSource = libraryRemoteDataSource, - filteredLibraryLocalDataSource = filteredLibraryLocalDataSource, - libraryFilter = filter, + getNovels = { lastUserNovelId -> getUserNovels(lastUserNovelId, filter) }, + deleteAllNovels = libraryLocalDataSource::deleteAllNovels, + insertNovels = libraryLocalDataSource::insertNovels, ), - pagingSourceFactory = filteredLibraryLocalDataSource::selectAllNovels, - ).flow.map { pagingData -> - pagingData.map(InDatabaseFilteredNovelEntity::toData) - } + pagingSourceFactory = libraryLocalDataSource::selectAllNovels, + ).flow } + + private suspend fun getUserNovels( + lastUserNovelId: Long, + libraryFilter: LibraryFilter, + ) = runCatching { + libraryRemoteDataSource.getUserNovels( + userId = accountRepository.userId, + lastUserNovelId = lastUserNovelId, + size = PAGE_SIZE, + sortCriteria = libraryFilter.sortCriteria, + isInterest = if (!libraryFilter.isInterested) null else true, + readStatuses = libraryFilter.readStatuses.ifEmpty { null }, + attractivePoints = libraryFilter.attractivePoints.ifEmpty { null }, + novelRating = if (libraryFilter.novelRating.isCloseTo(DEFAULT_NOVEL_RATING)) { + null + } else { + libraryFilter.novelRating + }, + query = null, + updatedSince = null, + ) + } } diff --git a/data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt b/data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt index 5b2fba9cb..a3e049c52 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt @@ -3,7 +3,10 @@ package com.into.websoso.data.library.repository import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import com.into.websoso.core.common.extensions.isCloseTo import com.into.websoso.data.filter.FilterRepository +import com.into.websoso.data.filter.model.DEFAULT_NOVEL_RATING +import com.into.websoso.data.filter.model.LibraryFilter import com.into.websoso.data.library.LibraryRepository import com.into.websoso.data.library.LibraryRepository.Companion.PAGE_SIZE import com.into.websoso.data.library.datasource.LibraryRemoteDataSource @@ -36,11 +39,33 @@ internal class UserLibraryRepository config = PagingConfig(pageSize = PAGE_SIZE), pagingSourceFactory = { LibraryPagingSource( - userId = userId, - libraryRemoteDataSource = libraryRemoteDataSource, - filterParams = filter, + getNovels = { lastUserNovelId -> + getUserNovels(lastUserNovelId, filter) + }, ) }, ).flow } + + private suspend fun getUserNovels( + lastUserNovelId: Long, + libraryFilter: LibraryFilter, + ) = runCatching { + libraryRemoteDataSource.getUserNovels( + userId = userId, + lastUserNovelId = lastUserNovelId, + size = PAGE_SIZE, + sortCriteria = libraryFilter.sortCriteria, + isInterest = if (!libraryFilter.isInterested) null else true, + readStatuses = libraryFilter.readStatuses.ifEmpty { null }, + attractivePoints = libraryFilter.attractivePoints.ifEmpty { null }, + novelRating = if (libraryFilter.novelRating.isCloseTo(DEFAULT_NOVEL_RATING)) { + null + } else { + libraryFilter.novelRating + }, + query = null, + updatedSince = null, + ) + } } diff --git a/domain/library/build.gradle.kts b/domain/library/build.gradle.kts index e3757d576..f5e75ee90 100644 --- a/domain/library/build.gradle.kts +++ b/domain/library/build.gradle.kts @@ -9,6 +9,6 @@ android { } dependencies { + implementation(projects.core.common) implementation(projects.data.library) - implementation(libs.paging.runtime) } diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoint.kt b/domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoint.kt new file mode 100644 index 000000000..e1abd56b7 --- /dev/null +++ b/domain/library/src/main/java/com/into/websoso/domain/library/model/AttractivePoint.kt @@ -0,0 +1,17 @@ +package com.into.websoso.domain.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? = AttractivePoint.entries.find { it.key == key } + } +} 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 58db9b640..b1ec1f9a4 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,19 +1,35 @@ package com.into.websoso.domain.library.model -enum class AttractivePoints( - val label: String, - val key: String, +data class AttractivePoints( + val value: Map = AttractivePoint.entries.associateWith { false }, ) { - WORLDVIEW("세계관", "worldview"), - MATERIAL("소재", "material"), - CHARACTER("캐릭터", "character"), - RELATIONSHIP("관계", "relationship"), - VIBE("분위기", "vibe"), - ; + val isSelected: Boolean + get() = value.any { it.value } + + val selectedAttractivePoints: List + get() = value.filterValues { it }.keys.toList() + + val selectedLabels: List + get() = selectedAttractivePoints.map { it.label } + + val selectedKeys: List + get() = selectedAttractivePoints.map { it.key } + + operator fun get(attractivePoint: AttractivePoint): Boolean = value[attractivePoint] ?: false + + fun set(attractivePoint: AttractivePoint): AttractivePoints { + if (!value.containsKey(attractivePoint)) return this + val updatedAttractivePoint = value.toMutableMap().apply { + this[attractivePoint] = !(this[attractivePoint] ?: false) + } + return this.copy(value = updatedAttractivePoint) + } companion object { - fun from(key: String): AttractivePoints = - AttractivePoints.entries.find { attractivePoints -> attractivePoints.key == key } - ?: WORLDVIEW + fun List.toAttractivePoints(): AttractivePoints { + val enabledSet = mapNotNull(AttractivePoint::from).toSet() + val mapped = AttractivePoint.entries.associateWith { it in enabledSet } + return AttractivePoints(mapped) + } } } diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/model/NovelRating.kt b/domain/library/src/main/java/com/into/websoso/domain/library/model/NovelRating.kt new file mode 100644 index 000000000..e2620f8d3 --- /dev/null +++ b/domain/library/src/main/java/com/into/websoso/domain/library/model/NovelRating.kt @@ -0,0 +1,23 @@ +package com.into.websoso.domain.library.model + +import com.into.websoso.core.common.extensions.isCloseTo + +data class NovelRating( + val rating: Rating = Rating.DEFAULT, +) { + val isSelected: Boolean + get() = rating != Rating.DEFAULT + + fun isCloseTo(other: Rating): Boolean = rating.value.isCloseTo(other.value) + + fun set(newRating: Rating): NovelRating = + if (rating == newRating) { + copy(rating = Rating.DEFAULT) + } else { + copy(rating = newRating) + } + + companion object { + fun from(value: Float): NovelRating = NovelRating(Rating.from(value)) + } +} diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/model/Rating.kt b/domain/library/src/main/java/com/into/websoso/domain/library/model/Rating.kt new file mode 100644 index 000000000..985097c73 --- /dev/null +++ b/domain/library/src/main/java/com/into/websoso/domain/library/model/Rating.kt @@ -0,0 +1,18 @@ +package com.into.websoso.domain.library.model + +import com.into.websoso.core.common.extensions.isCloseTo + +enum class Rating( + val value: Float, +) { + DEFAULT(0.0f), + THREE_POINT_FIVE(3.5f), + FOUR(4.0f), + FOUR_POINT_FIVE(4.5f), + FOUR_POINT_EIGHT(4.8f), + ; + + companion object { + fun from(value: Float): Rating = entries.find { it.value.isCloseTo(value) } ?: DEFAULT + } +} diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt b/domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt index 53a83da4a..da1f905ee 100644 --- a/domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt +++ b/domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatus.kt @@ -2,15 +2,14 @@ package com.into.websoso.domain.library.model enum class ReadStatus( val key: String, + val label: String, ) { - WATCHING("WATCHING"), - WATCHED("WATCHED"), - QUIT("QUIT"), + WATCHING("WATCHING", "보는중"), + WATCHED("WATCHED", "봤어요"), + QUIT("QUIT", "하차함"), ; companion object { - fun from(key: String): ReadStatus = - entries.find { readStatus -> readStatus.key == key } - ?: throw IllegalArgumentException() + fun from(key: String): ReadStatus? = entries.find { it.key == key } } } diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatuses.kt b/domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatuses.kt new file mode 100644 index 000000000..f7476bca8 --- /dev/null +++ b/domain/library/src/main/java/com/into/websoso/domain/library/model/ReadStatuses.kt @@ -0,0 +1,32 @@ +package com.into.websoso.domain.library.model + +data class ReadStatuses( + val value: Map = ReadStatus.entries.associateWith { false }, +) { + val isSelected: Boolean + get() = value.any { it.value } + + val selectedLabels: List + get() = value.filterValues { it }.keys.map { it.label } + + val selectedKeys: List + get() = value.filterValues { it }.keys.map { it.key } + + operator fun get(readStatus: ReadStatus): Boolean = value[readStatus] ?: false + + fun set(readStatus: ReadStatus): ReadStatuses { + if (!value.containsKey(readStatus)) return this + val updatedReadStatus = value.toMutableMap().apply { + this[readStatus] = !(this[readStatus] ?: false) + } + return this.copy(value = updatedReadStatus) + } + + companion object { + fun List.toReadStatuses(): ReadStatuses { + val enabled = mapNotNull(ReadStatus::from).toSet() + val mapped = ReadStatus.entries.associateWith { it in enabled } + return ReadStatuses(mapped) + } + } +} diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/model/SortCriteria.kt b/domain/library/src/main/java/com/into/websoso/domain/library/model/SortCriteria.kt new file mode 100644 index 000000000..611d7b485 --- /dev/null +++ b/domain/library/src/main/java/com/into/websoso/domain/library/model/SortCriteria.kt @@ -0,0 +1,14 @@ +package com.into.websoso.domain.library.model + +enum class SortCriteria( + val key: String, + val label: String, +) { + RECENT("RECENT", "최신 순"), + OLD("OLD", "오래된 순"), + ; + + companion object { + fun from(key: String): SortCriteria = entries.find { it.name == key } ?: RECENT + } +} diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/model/SortType.kt b/domain/library/src/main/java/com/into/websoso/domain/library/model/SortType.kt deleted file mode 100644 index 68b8bf248..000000000 --- a/domain/library/src/main/java/com/into/websoso/domain/library/model/SortType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.into.websoso.domain.library.model - -enum class SortType { - RECENT, - OLD, -} 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 79d26317e..6dca87d11 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 @@ -30,7 +30,8 @@ import androidx.paging.map import com.into.websoso.core.common.extensions.collectAsEventWithLifecycle import com.into.websoso.core.designsystem.theme.White import com.into.websoso.data.library.model.NovelEntity -import com.into.websoso.domain.library.model.AttractivePoints +import com.into.websoso.domain.library.model.AttractivePoint +import com.into.websoso.domain.library.model.Rating import com.into.websoso.domain.library.model.ReadStatus import com.into.websoso.feature.library.component.LibraryEmptyView import com.into.websoso.feature.library.component.LibraryFilterEmptyView @@ -40,11 +41,9 @@ import com.into.websoso.feature.library.component.LibraryList import com.into.websoso.feature.library.component.LibraryTopBar import com.into.websoso.feature.library.filter.LibraryFilterBottomSheetScreen import com.into.websoso.feature.library.mapper.toUiModel -import com.into.websoso.feature.library.model.LibraryFilterType -import com.into.websoso.feature.library.model.LibraryFilterUiState -import com.into.websoso.feature.library.model.LibraryListItemModel +import com.into.websoso.feature.library.model.LibraryFilterUiModel import com.into.websoso.feature.library.model.LibraryUiState -import com.into.websoso.feature.library.model.RatingLevelUiModel +import com.into.websoso.feature.library.model.NovelUiModel import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -120,24 +119,24 @@ fun LibraryScreen( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun LibraryScreen( - novels: LazyPagingItems, + novels: LazyPagingItems, uiState: LibraryUiState, - filterUiState: LibraryFilterUiState, + filterUiState: LibraryFilterUiModel, listState: LazyListState, gridState: LazyGridState, sheetState: SheetState, isShowBottomSheet: Boolean, onDismissRequest: () -> Unit, - onFilterClick: (LibraryFilterType) -> Unit, + onFilterClick: () -> Unit, onSortClick: () -> Unit, onToggleViewType: () -> Unit, - onItemClick: (LibraryListItemModel) -> Unit, + onItemClick: (NovelUiModel) -> Unit, onSearchClick: () -> Unit, onExploreClick: () -> Unit, onInterestClick: () -> Unit, - onAttractivePointClick: (AttractivePoints) -> Unit, + onAttractivePointClick: (AttractivePoint) -> Unit, onReadStatusClick: (ReadStatus) -> Unit, - onRatingClick: (rating: RatingLevelUiModel) -> Unit, + onRatingClick: (rating: Rating) -> Unit, onResetClick: () -> Unit, onFilterSearchClick: () -> Unit, ) { @@ -154,7 +153,7 @@ private fun LibraryScreen( Spacer(modifier = Modifier.height(28.dp)) LibraryFilterTopBar( - libraryFilterUiState = uiState.libraryFilterUiState, + libraryFilterUiModel = uiState.libraryFilterUiModel, totalCount = novels.itemCount, isGrid = uiState.isGrid, onFilterClick = onFilterClick, @@ -168,7 +167,7 @@ private fun LibraryScreen( when { novels.itemCount == 0 && novels.loadState.refresh !is LoadState.Loading -> { - if (uiState.libraryFilterUiState.isFilterApplied) { + if (uiState.libraryFilterUiModel.isFilterApplied) { LibraryFilterEmptyView() } else { LibraryEmptyView(onExploreClick = onExploreClick) 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 3ac822731..a2184c4b6 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 @@ -4,16 +4,18 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn -import com.into.websoso.core.common.extensions.isCloseTo import com.into.websoso.data.filter.FilterRepository import com.into.websoso.data.library.LibraryRepository import com.into.websoso.data.library.model.NovelEntity -import com.into.websoso.domain.library.model.AttractivePoints +import com.into.websoso.domain.library.model.AttractivePoint +import com.into.websoso.domain.library.model.AttractivePoints.Companion.toAttractivePoints +import com.into.websoso.domain.library.model.NovelRating +import com.into.websoso.domain.library.model.Rating import com.into.websoso.domain.library.model.ReadStatus -import com.into.websoso.feature.library.model.LibraryFilterUiState +import com.into.websoso.domain.library.model.ReadStatuses.Companion.toReadStatuses +import com.into.websoso.domain.library.model.SortCriteria +import com.into.websoso.feature.library.model.LibraryFilterUiModel import com.into.websoso.feature.library.model.LibraryUiState -import com.into.websoso.feature.library.model.RatingLevelUiModel -import com.into.websoso.feature.library.model.SortTypeUiModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -35,7 +37,7 @@ class LibraryViewModel private val _uiState = MutableStateFlow(LibraryUiState()) val uiState: StateFlow = _uiState.asStateFlow() - private val _tempFilterUiState = MutableStateFlow(uiState.value.libraryFilterUiState) + private val _tempFilterUiState = MutableStateFlow(uiState.value.libraryFilterUiModel) val tempFilterUiState = _tempFilterUiState.asStateFlow() private val _scrollToTopEvent = Channel(Channel.BUFFERED) @@ -53,18 +55,12 @@ class LibraryViewModel filterRepository.filterFlow.collect { filter -> _uiState.update { uiState -> uiState.copy( - libraryFilterUiState = uiState.libraryFilterUiState.copy( - selectedSortType = SortTypeUiModel.valueOf(filter.sortCriteria), + libraryFilterUiModel = uiState.libraryFilterUiModel.copy( + sortCriteria = SortCriteria.from(filter.sortCriteria), isInterested = filter.isInterested, - readStatuses = filter.readStatuses - .mapKeys { - ReadStatus.from(it.key) - }.ifEmpty { uiState.libraryFilterUiState.readStatuses }, - attractivePoints = filter.attractivePoints - .mapKeys { - AttractivePoints.from(it.key) - }.ifEmpty { uiState.libraryFilterUiState.attractivePoints }, - novelRating = filter.novelRating, + readStatuses = filter.readStatuses.toReadStatuses(), + attractivePoints = filter.attractivePoints.toAttractivePoints(), + novelRating = NovelRating.from(filter.novelRating), ), ) } @@ -85,21 +81,21 @@ class LibraryViewModel } fun updateSortType() { - val current = uiState.value.libraryFilterUiState.selectedSortType - val newSortType = when (current) { - SortTypeUiModel.RECENT -> SortTypeUiModel.OLD - SortTypeUiModel.OLD -> SortTypeUiModel.RECENT + val current = uiState.value.libraryFilterUiModel.sortCriteria + val updatedSortCriteria = when (current) { + SortCriteria.RECENT -> SortCriteria.OLD + SortCriteria.OLD -> SortCriteria.RECENT } viewModelScope.launch { filterRepository.updateFilter( - sortCriteria = newSortType.name, + sortCriteria = updatedSortCriteria.key, ) } } fun updateInterestedNovels() { - val updatedInterested = !uiState.value.libraryFilterUiState.isInterested + val updatedInterested = !uiState.value.libraryFilterUiModel.isInterested viewModelScope.launch { filterRepository.updateFilter( @@ -110,50 +106,40 @@ class LibraryViewModel fun updateMyLibraryFilter() { _tempFilterUiState.update { - uiState.value.libraryFilterUiState + uiState.value.libraryFilterUiModel } } fun updateReadStatus(readStatus: ReadStatus) { _tempFilterUiState.update { - it.copy( - readStatuses = it.readStatuses.mapValues { (key, value) -> - if (key == readStatus) !value else value - }, - ) + it.copy(readStatuses = it.readStatuses.set(readStatus)) } } - fun updateAttractivePoints(attractivePoint: AttractivePoints) { + fun updateAttractivePoints(attractivePoint: AttractivePoint) { _tempFilterUiState.update { - it.copy( - attractivePoints = it.attractivePoints.mapValues { (key, value) -> - if (key == attractivePoint) !value else value - }, - ) + it.copy(attractivePoints = it.attractivePoints.set(attractivePoint)) } } - fun updateRating(rating: RatingLevelUiModel) { + fun updateRating(rating: Rating) { _tempFilterUiState.update { - it.copy( - novelRating = if (it.novelRating.isCloseTo(rating.value)) 0f else rating.value, - ) + it.copy(novelRating = it.novelRating.set(rating)) } } fun resetFilter() { _tempFilterUiState.update { - LibraryFilterUiState() + LibraryFilterUiModel() } } fun searchFilteredNovels() { viewModelScope.launch { filterRepository.updateFilter( - readStatuses = _tempFilterUiState.value.readStatuses.mapKeys { it.key.key }, - attractivePoints = _tempFilterUiState.value.attractivePoints.mapKeys { it.key.key }, - novelRating = _tempFilterUiState.value.novelRating, + readStatuses = _tempFilterUiState.value.readStatuses.selectedKeys, + attractivePoints = _tempFilterUiState.value.attractivePoints.selectedKeys, + novelRating = _tempFilterUiState.value.novelRating.rating.value, ) } } 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 9be9d962c..080a1a089 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 @@ -14,14 +14,14 @@ 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 +import com.into.websoso.feature.library.model.NovelUiModel @Composable internal fun LibraryGridList( - novels: LazyPagingItems, + novels: LazyPagingItems, gridState: LazyGridState, modifier: Modifier = Modifier, - onItemClick: (LibraryListItemModel) -> Unit = {}, + onItemClick: (NovelUiModel) -> Unit = {}, ) { LazyVerticalGrid( columns = GridCells.Fixed(3), 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 622921a19..0932646ae 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 @@ -36,8 +36,8 @@ 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.NovelUiModel +import com.into.websoso.feature.library.model.RatingStarUiModel import com.into.websoso.feature.library.model.ReadStatusUiModel private const val GRID_COLUMN_COUNT = 3 @@ -48,7 +48,7 @@ private const val IMAGE_ASPECT_HEIGHT = 160f @Composable internal fun NovelGridListItem( - item: LibraryListItemModel, + item: NovelUiModel, modifier: Modifier = Modifier, onItemClick: () -> Unit = {}, ) { @@ -90,7 +90,7 @@ internal fun NovelGridListItem( @Composable private fun NovelGridThumbnail( - item: LibraryListItemModel, + item: NovelUiModel, size: GridItemSize, ) { Box( @@ -107,7 +107,7 @@ private fun NovelGridThumbnail( item.readStatus?.let { ReadStatusBadge( - readStatus = it, + readStatusUiModel = it, modifier = Modifier .align(Alignment.BottomStart) .padding(6.dp), @@ -129,20 +129,20 @@ private fun NovelGridThumbnail( @Composable private fun ReadStatusBadge( - readStatus: ReadStatusUiModel, + readStatusUiModel: ReadStatusUiModel, modifier: Modifier = Modifier, ) { Box( modifier = modifier .width(48.dp) .background( - color = readStatus.backgroundColor, + color = readStatusUiModel.backgroundColor, shape = RoundedCornerShape(4.dp), ).padding(vertical = 4.dp), contentAlignment = Alignment.Center, ) { Text( - text = readStatus.label, + text = readStatusUiModel.readStatus.label, color = White, style = WebsosoTheme.typography.label2, ) @@ -168,7 +168,7 @@ private fun rememberGridItemSize(): GridItemSize { @Composable internal fun NovelRatingStar( - stars: List, + stars: List, modifier: Modifier = Modifier, ) { Row( @@ -186,11 +186,11 @@ internal fun NovelRatingStar( } @Composable -private fun ratingStarIcon(starType: RatingStarType): ImageVector { +private fun ratingStarIcon(starType: RatingStarUiModel): ImageVector { val resId = when (starType) { - RatingStarType.FULL -> ic_storage_star - RatingStarType.HALF -> ic_library_half_star - RatingStarType.EMPTY -> ic_library_null_star + RatingStarUiModel.FULL -> ic_storage_star + RatingStarUiModel.HALF -> ic_library_half_star + RatingStarUiModel.EMPTY -> ic_library_null_star } return ImageVector.vectorResource(id = resId) } 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 9c97e8250..082133aa1 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 @@ -9,13 +9,13 @@ 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 +import com.into.websoso.feature.library.model.NovelUiModel @Composable internal fun LibraryList( - novels: LazyPagingItems, + novels: LazyPagingItems, listState: LazyListState, - onItemClick: (LibraryListItemModel) -> Unit = {}, + onItemClick: (NovelUiModel) -> Unit = {}, modifier: Modifier = Modifier, ) { LazyColumn( 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 67bebff12..39b270b85 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 @@ -53,9 +53,10 @@ 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.AttractivePoint import com.into.websoso.domain.library.model.AttractivePoints -import com.into.websoso.feature.library.model.AttractivePointUiModel -import com.into.websoso.feature.library.model.LibraryListItemModel +import com.into.websoso.domain.library.model.NovelRating +import com.into.websoso.feature.library.model.NovelUiModel import com.into.websoso.feature.library.model.ReadStatusUiModel private const val THUMBNAIL_WIDTH_RATIO = 60f / 360f @@ -64,7 +65,7 @@ private const val FEED_CARD_WIDTH_RATIO = 0.8611f @Composable internal fun LibraryListItem( - item: LibraryListItemModel, + item: NovelUiModel, modifier: Modifier = Modifier, onClick: () -> Unit = {}, ) { @@ -117,7 +118,7 @@ private fun NovelThumbnail( Column(modifier = Modifier.width(size.width)) { ReadStatusBadge( - readStatus = readStatus, + readStatusUiModel = readStatus, width = size.width, ) @@ -151,22 +152,22 @@ private fun NovelThumbnail( @Composable private fun ReadStatusBadge( - readStatus: ReadStatusUiModel?, + readStatusUiModel: ReadStatusUiModel?, width: Dp, ) { - if (readStatus != null) { + readStatusUiModel?.let { Box( modifier = Modifier .width(width) .background( - color = readStatus.backgroundColor, + color = it.backgroundColor, shape = RoundedCornerShape(8.dp), ) .padding(vertical = 4.dp), contentAlignment = Alignment.Center, ) { Text( - text = readStatus.label, + text = it.readStatus.label, color = White, style = WebsosoTheme.typography.label2, ) @@ -184,7 +185,7 @@ private fun calculateThumbnailSize(): ThumbnailUiSize { } @Composable -private fun NovelInfo(item: LibraryListItemModel) { +private fun NovelInfo(item: NovelUiModel) { Column { Spacer(modifier = Modifier.height(2.dp)) NovelInfoDate(item = item) @@ -199,7 +200,7 @@ private fun NovelInfo(item: LibraryListItemModel) { } @Composable -private fun NovelInfoDate(item: LibraryListItemModel) { +private fun NovelInfoDate(item: NovelUiModel) { Box(modifier = Modifier.height(18.dp)) { item.formattedDateRange?.let { Text( @@ -214,9 +215,9 @@ private fun NovelInfoDate(item: LibraryListItemModel) { @Composable private fun NovelInfoContent( title: String, - myRating: Float?, - totalRating: Float, - attractivePoints: List, + myRating: NovelRating?, + totalRating: NovelRating, + attractivePoints: AttractivePoints, ) { val size = calculateThumbnailSize() @@ -239,8 +240,8 @@ private fun NovelInfoContent( Spacer(modifier = Modifier.height(10.dp)) - if (attractivePoints.isNotEmpty()) { - AttractivePointTags(types = attractivePoints) + if (attractivePoints.value.isNotEmpty()) { + AttractivePointTags(attractivePoints = attractivePoints) } else { Box(modifier = Modifier.height(18.dp)) } @@ -249,15 +250,15 @@ private fun NovelInfoContent( @Composable private fun NovelRatings( - myRating: Float?, - totalRating: Float, + myRating: NovelRating?, + totalRating: NovelRating, ) { Row(verticalAlignment = Alignment.CenterVertically) { myRating?.let { - MyRatingSection(rating = it) + MyRatingSection(rating = it.rating.value) Spacer(modifier = Modifier.width(10.dp)) } - TotalRatingSection(rating = totalRating) + TotalRatingSection(rating = totalRating.rating.value) } } @@ -305,12 +306,12 @@ private fun TotalRatingSection(rating: Float) { } @Composable -private fun AttractivePointTags(types: List) { +private fun AttractivePointTags(attractivePoints: AttractivePoints) { Row(verticalAlignment = Alignment.CenterVertically) { - types.forEachIndexed { index, type -> - AttractivePointItem(type) + attractivePoints.selectedAttractivePoints.forEachIndexed { index, attractivePoint -> + AttractivePointItem(attractivePoint) - if (index < types.lastIndex) { + if (index < attractivePoints.selectedAttractivePoints.lastIndex) { Spacer(modifier = Modifier.width(6.dp)) Text( @@ -326,18 +327,18 @@ private fun AttractivePointTags(types: List) { } @Composable -private fun AttractivePointItem(type: AttractivePointUiModel) { +private fun AttractivePointItem(attractivePoint: AttractivePoint) { Row(verticalAlignment = Alignment.CenterVertically) { Image( - imageVector = attractivePointIcon(type), - contentDescription = type.label, + imageVector = attractivePointIcon(attractivePoint), + contentDescription = attractivePoint.label, modifier = Modifier.size(16.dp), ) Spacer(modifier = Modifier.width(4.dp)) Text( - text = type.label, + text = attractivePoint.label, style = WebsosoTheme.typography.body4, color = Gray300, ) @@ -345,13 +346,13 @@ private fun AttractivePointItem(type: AttractivePointUiModel) { } @Composable -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 +private fun attractivePointIcon(attractivePoint: AttractivePoint): ImageVector { + val resId = when (attractivePoint) { + 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) } 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 8b36ca3b1..49769f998 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 @@ -39,15 +39,14 @@ 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.model.LibraryFilterType -import com.into.websoso.feature.library.model.LibraryFilterUiState -import com.into.websoso.feature.library.model.SortTypeUiModel +import com.into.websoso.domain.library.model.SortCriteria +import com.into.websoso.feature.library.model.LibraryFilterUiModel @Composable internal fun LibraryFilterTopBar( - libraryFilterUiState: LibraryFilterUiState, + libraryFilterUiModel: LibraryFilterUiModel, totalCount: Int, - onFilterClick: (LibraryFilterType) -> Unit, + onFilterClick: () -> Unit, onSortClick: () -> Unit, isGrid: Boolean, onToggleViewType: () -> Unit, @@ -60,7 +59,7 @@ internal fun LibraryFilterTopBar( .padding(start = 20.dp), ) { NovelFilterChipSection( - libraryFilterUiState = libraryFilterUiState, + libraryFilterUiModel = libraryFilterUiModel, onFilterClick = onFilterClick, onInterestClick = onInterestClick, ) @@ -69,7 +68,7 @@ internal fun LibraryFilterTopBar( NovelFilterStatusBar( totalCount = totalCount, - selectedSortType = libraryFilterUiState.selectedSortType, + sortCriteria = libraryFilterUiModel.sortCriteria, isGrid = isGrid, onSortClick = onSortClick, onToggleViewType = onToggleViewType, @@ -79,9 +78,9 @@ internal fun LibraryFilterTopBar( @Composable private fun NovelFilterChipSection( - libraryFilterUiState: LibraryFilterUiState, + libraryFilterUiModel: LibraryFilterUiModel, onInterestClick: () -> Unit, - onFilterClick: (LibraryFilterType) -> Unit, + onFilterClick: () -> Unit, ) { Row( modifier = Modifier @@ -90,7 +89,7 @@ private fun NovelFilterChipSection( ) { NovelFilterChip( text = "관심", - isSelected = libraryFilterUiState.isInterested, + isSelected = libraryFilterUiModel.isInterested, onClick = onInterestClick, showDropdownIcon = false, ) @@ -103,21 +102,21 @@ private fun NovelFilterChipSection( .background(color = Gray70), ) NovelFilterChip( - text = libraryFilterUiState.readStatusLabelText, - isSelected = libraryFilterUiState.readStatuses.any { it.value }, - onClick = { onFilterClick(LibraryFilterType.ReadStatus) }, + text = libraryFilterUiModel.readStatusLabelText, + isSelected = libraryFilterUiModel.readStatuses.isSelected, + onClick = onFilterClick, ) NovelFilterChip( - text = libraryFilterUiState.ratingText, - isSelected = libraryFilterUiState.isRatingSelected, - onClick = { onFilterClick(LibraryFilterType.Rating) }, + text = libraryFilterUiModel.ratingText, + isSelected = libraryFilterUiModel.novelRating.isSelected, + onClick = onFilterClick, ) NovelFilterChip( - text = libraryFilterUiState.attractivePointLabelText, - isSelected = libraryFilterUiState.attractivePoints.any { it.value }, - onClick = { onFilterClick(LibraryFilterType.AttractivePoint) }, + text = libraryFilterUiModel.attractivePointLabelText, + isSelected = libraryFilterUiModel.attractivePoints.isSelected, + onClick = onFilterClick, ) } } @@ -165,7 +164,7 @@ private fun NovelFilterChip( @Composable private fun NovelFilterStatusBar( totalCount: Int, - selectedSortType: SortTypeUiModel, + sortCriteria: SortCriteria, isGrid: Boolean, onSortClick: () -> Unit, onToggleViewType: () -> Unit, @@ -185,7 +184,7 @@ private fun NovelFilterStatusBar( Row(verticalAlignment = Alignment.CenterVertically) { SortTypeSelector( - selectedSortType = selectedSortType, + sortCriteria = sortCriteria, onClick = onSortClick, ) @@ -195,8 +194,7 @@ private fun NovelFilterStatusBar( id = if (isGrid) ic_library_grid else ic_library_list, ), contentDescription = null, - modifier = Modifier.size(12.dp) - , + modifier = Modifier.size(12.dp), ) } } @@ -205,7 +203,7 @@ private fun NovelFilterStatusBar( @Composable private fun SortTypeSelector( - selectedSortType: SortTypeUiModel, + sortCriteria: SortCriteria, onClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -224,7 +222,7 @@ private fun SortTypeSelector( Spacer(modifier = Modifier.width(4.dp)) Text( - text = selectedSortType.displayName, + text = sortCriteria.label, style = WebsosoTheme.typography.body5, color = Gray300, ) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterBottomSheetScreen.kt b/feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterBottomSheetScreen.kt index 907f5d355..d5bc22470 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterBottomSheetScreen.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterBottomSheetScreen.kt @@ -19,25 +19,28 @@ import androidx.compose.ui.unit.dp import com.into.websoso.core.designsystem.theme.Black import com.into.websoso.core.designsystem.theme.WebsosoTheme import com.into.websoso.core.designsystem.theme.White +import com.into.websoso.domain.library.model.AttractivePoint import com.into.websoso.domain.library.model.AttractivePoints +import com.into.websoso.domain.library.model.NovelRating +import com.into.websoso.domain.library.model.Rating import com.into.websoso.domain.library.model.ReadStatus +import com.into.websoso.domain.library.model.ReadStatuses import com.into.websoso.feature.library.filter.component.LibraryFilterBottomSheetAttractivePoints import com.into.websoso.feature.library.filter.component.LibraryFilterBottomSheetButtons import com.into.websoso.feature.library.filter.component.LibraryFilterBottomSheetHeader import com.into.websoso.feature.library.filter.component.LibraryFilterBottomSheetNovelRatingGrid import com.into.websoso.feature.library.filter.component.LibraryFilterBottomSheetReadStatus -import com.into.websoso.feature.library.model.LibraryFilterUiState -import com.into.websoso.feature.library.model.RatingLevelUiModel +import com.into.websoso.feature.library.model.LibraryFilterUiModel @Composable @OptIn(ExperimentalMaterial3Api::class) internal fun LibraryFilterBottomSheetScreen( - filterUiState: LibraryFilterUiState, + filterUiState: LibraryFilterUiModel, onDismissRequest: () -> Unit, sheetState: SheetState, - onAttractivePointClick: (AttractivePoints) -> Unit, + onAttractivePointClick: (AttractivePoint) -> Unit, onReadStatusClick: (ReadStatus) -> Unit, - onRatingClick: (rating: RatingLevelUiModel) -> Unit, + onRatingClick: (rating: Rating) -> Unit, onResetClick: () -> Unit, onFilterSearchClick: () -> Unit, ) { @@ -60,12 +63,12 @@ internal fun LibraryFilterBottomSheetScreen( private fun LibraryFilterBottomSheetScreen( onDismissRequest: () -> Unit, sheetState: SheetState, - readStatues: Map, - attractivePoints: Map, - onAttractivePointClick: (AttractivePoints) -> Unit, + readStatues: ReadStatuses, + attractivePoints: AttractivePoints, + onAttractivePointClick: (AttractivePoint) -> Unit, onReadStatusClick: (ReadStatus) -> Unit, - selectedRating: Float, - onRatingClick: (rating: RatingLevelUiModel) -> Unit, + selectedRating: NovelRating, + onRatingClick: (rating: Rating) -> Unit, onResetClick: () -> Unit, onFilterSearchClick: () -> Unit, ) { @@ -140,7 +143,7 @@ private fun LibraryFilterBottomSheetPreview() { initialValue = SheetValue.Expanded, skipHiddenState = false, ), - filterUiState = LibraryFilterUiState(), + filterUiState = LibraryFilterUiModel(), onAttractivePointClick = { }, onReadStatusClick = { }, onRatingClick = { }, diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetAttractivePoints.kt b/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetAttractivePoints.kt index 0229ca834..eb009c86b 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetAttractivePoints.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetAttractivePoints.kt @@ -15,18 +15,19 @@ import com.into.websoso.core.resource.R.drawable.ic_library_material 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.domain.library.model.AttractivePoint +import com.into.websoso.domain.library.model.AttractivePoint.CHARACTER +import com.into.websoso.domain.library.model.AttractivePoint.MATERIAL +import com.into.websoso.domain.library.model.AttractivePoint.RELATIONSHIP +import com.into.websoso.domain.library.model.AttractivePoint.VIBE +import com.into.websoso.domain.library.model.AttractivePoint.WORLDVIEW import com.into.websoso.domain.library.model.AttractivePoints -import com.into.websoso.domain.library.model.AttractivePoints.CHARACTER -import com.into.websoso.domain.library.model.AttractivePoints.MATERIAL -import com.into.websoso.domain.library.model.AttractivePoints.RELATIONSHIP -import com.into.websoso.domain.library.model.AttractivePoints.VIBE -import com.into.websoso.domain.library.model.AttractivePoints.WORLDVIEW @SuppressLint("ResourceType") @Composable internal fun LibraryFilterBottomSheetAttractivePoints( - attractivePoints: Map, - onAttractivePointClick: (AttractivePoints) -> Unit, + attractivePoints: AttractivePoints, + onAttractivePointClick: (AttractivePoint) -> Unit, ) { Row( horizontalArrangement = Arrangement.SpaceBetween, @@ -38,7 +39,7 @@ internal fun LibraryFilterBottomSheetAttractivePoints( iconTitle = "세계관", horizontalPadding = 12.dp, iconSize = 36.dp, - isSelected = attractivePoints[WORLDVIEW] ?: false, + isSelected = attractivePoints[WORLDVIEW], onClick = { onAttractivePointClick(WORLDVIEW) }, ) LibraryFilterBottomSheetClickableItem( @@ -46,7 +47,7 @@ internal fun LibraryFilterBottomSheetAttractivePoints( iconTitle = "소재", horizontalPadding = 12.dp, iconSize = 36.dp, - isSelected = attractivePoints[MATERIAL] ?: false, + isSelected = attractivePoints[MATERIAL], onClick = { onAttractivePointClick(MATERIAL) }, ) LibraryFilterBottomSheetClickableItem( @@ -54,7 +55,7 @@ internal fun LibraryFilterBottomSheetAttractivePoints( iconTitle = "캐릭터", horizontalPadding = 12.dp, iconSize = 36.dp, - isSelected = attractivePoints[CHARACTER] ?: false, + isSelected = attractivePoints[CHARACTER], onClick = { onAttractivePointClick(CHARACTER) }, ) LibraryFilterBottomSheetClickableItem( @@ -62,7 +63,7 @@ internal fun LibraryFilterBottomSheetAttractivePoints( iconTitle = "관계", horizontalPadding = 12.dp, iconSize = 36.dp, - isSelected = attractivePoints[RELATIONSHIP] ?: false, + isSelected = attractivePoints[RELATIONSHIP], onClick = { onAttractivePointClick(RELATIONSHIP) }, ) LibraryFilterBottomSheetClickableItem( @@ -70,7 +71,7 @@ internal fun LibraryFilterBottomSheetAttractivePoints( iconTitle = "분위기", horizontalPadding = 12.dp, iconSize = 36.dp, - isSelected = attractivePoints[VIBE] ?: false, + isSelected = attractivePoints[VIBE], onClick = { onAttractivePointClick(VIBE) }, ) } @@ -81,7 +82,7 @@ internal fun LibraryFilterBottomSheetAttractivePoints( private fun LibraryFilterBottomSheetAttractivePointsPreview() { WebsosoTheme { LibraryFilterBottomSheetAttractivePoints( - attractivePoints = mapOf(), + attractivePoints = AttractivePoints(), onAttractivePointClick = { }, ) } diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetNovelRatingGrid.kt b/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetNovelRatingGrid.kt index 3543f612e..642a8fa01 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetNovelRatingGrid.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetNovelRatingGrid.kt @@ -16,22 +16,22 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.into.websoso.core.common.extensions.isCloseTo 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.Primary50 import com.into.websoso.core.designsystem.theme.WebsosoTheme -import com.into.websoso.feature.library.model.RatingLevelUiModel -import com.into.websoso.feature.library.model.RatingLevelUiModel.FOUR -import com.into.websoso.feature.library.model.RatingLevelUiModel.FOUR_POINT_EIGHT -import com.into.websoso.feature.library.model.RatingLevelUiModel.FOUR_POINT_FIVE -import com.into.websoso.feature.library.model.RatingLevelUiModel.THREE_POINT_FIVE +import com.into.websoso.domain.library.model.NovelRating +import com.into.websoso.domain.library.model.Rating +import com.into.websoso.domain.library.model.Rating.FOUR +import com.into.websoso.domain.library.model.Rating.FOUR_POINT_EIGHT +import com.into.websoso.domain.library.model.Rating.FOUR_POINT_FIVE +import com.into.websoso.domain.library.model.Rating.THREE_POINT_FIVE @Composable internal fun LibraryFilterBottomSheetNovelRatingGrid( - selectedRating: Float, - onRatingClick: (rating: RatingLevelUiModel) -> Unit, + selectedRating: NovelRating, + onRatingClick: (rating: Rating) -> Unit, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -50,7 +50,7 @@ internal fun LibraryFilterBottomSheetNovelRatingGrid( .clickable { onRatingClick(THREE_POINT_FIVE) }, - isSelected = selectedRating.isCloseTo(THREE_POINT_FIVE.value), + isSelected = selectedRating.isCloseTo(THREE_POINT_FIVE), ) NovelRatingItem( title = "4.0 이상", @@ -59,7 +59,7 @@ internal fun LibraryFilterBottomSheetNovelRatingGrid( .clickable { onRatingClick(FOUR) }, - isSelected = selectedRating.isCloseTo(FOUR.value), + isSelected = selectedRating.isCloseTo(FOUR), ) } Row( @@ -74,7 +74,7 @@ internal fun LibraryFilterBottomSheetNovelRatingGrid( .clickable { onRatingClick(FOUR_POINT_FIVE) }, - isSelected = selectedRating.isCloseTo(FOUR_POINT_FIVE.value), + isSelected = selectedRating.isCloseTo(FOUR_POINT_FIVE), ) NovelRatingItem( title = "4.8 이상", @@ -83,7 +83,7 @@ internal fun LibraryFilterBottomSheetNovelRatingGrid( .clickable { onRatingClick(FOUR_POINT_EIGHT) }, - isSelected = selectedRating.isCloseTo(FOUR_POINT_EIGHT.value), + isSelected = selectedRating.isCloseTo(FOUR_POINT_EIGHT), ) } } @@ -128,7 +128,7 @@ private fun NovelRatingItem( private fun LibraryFilterBottomSheetNovelRatingGridPreview() { WebsosoTheme { LibraryFilterBottomSheetNovelRatingGrid( - selectedRating = 0f, + selectedRating = NovelRating(), onRatingClick = {}, ) } diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetReadStatus.kt b/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetReadStatus.kt index 8ef36593a..206c9ccde 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetReadStatus.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/filter/component/LibraryFilterBottomSheetReadStatus.kt @@ -17,11 +17,12 @@ import com.into.websoso.domain.library.model.ReadStatus import com.into.websoso.domain.library.model.ReadStatus.QUIT import com.into.websoso.domain.library.model.ReadStatus.WATCHED import com.into.websoso.domain.library.model.ReadStatus.WATCHING +import com.into.websoso.domain.library.model.ReadStatuses @SuppressLint("ResourceType") @Composable internal fun LibraryFilterBottomSheetReadStatus( - readStatuses: Map, + readStatuses: ReadStatuses, onReadStatusClick: (ReadStatus) -> Unit, ) { Row( @@ -35,7 +36,7 @@ internal fun LibraryFilterBottomSheetReadStatus( iconSize = 24.dp, horizontalPadding = 36.dp, onClick = { onReadStatusClick(WATCHING) }, - isSelected = readStatuses[WATCHING] ?: false, + isSelected = readStatuses[WATCHING], ) LibraryFilterBottomSheetClickableItem( icon = ic_library_finished, @@ -43,7 +44,7 @@ internal fun LibraryFilterBottomSheetReadStatus( iconSize = 24.dp, horizontalPadding = 36.dp, onClick = { onReadStatusClick(WATCHED) }, - isSelected = readStatuses[WATCHED] ?: false, + isSelected = readStatuses[WATCHED], ) LibraryFilterBottomSheetClickableItem( icon = ic_library_stopped, @@ -51,7 +52,7 @@ internal fun LibraryFilterBottomSheetReadStatus( iconSize = 24.dp, horizontalPadding = 36.dp, onClick = { onReadStatusClick(QUIT) }, - isSelected = readStatuses[QUIT] ?: false, + isSelected = readStatuses[QUIT], ) } } @@ -61,7 +62,7 @@ internal fun LibraryFilterBottomSheetReadStatus( private fun LibraryFilterBottomSheetReadStatusPreview() { WebsosoTheme { LibraryFilterBottomSheetReadStatus( - readStatuses = mapOf(), + readStatuses = ReadStatuses(), onReadStatusClick = { }, ) } 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 deleted file mode 100644 index 0abc34795..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/mapper/AttractivePointsMapper.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.into.websoso.feature.library.mapper - -import com.into.websoso.domain.library.model.AttractivePoints -import com.into.websoso.feature.library.model.AttractivePointUiModel - -internal fun AttractivePoints.toUiModel(): AttractivePointUiModel = - AttractivePointUiModel( - type = this, - label = this.label, - key = this.key, - ) 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 deleted file mode 100644 index 03729b8ca..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/mapper/ListItemMapper.kt +++ /dev/null @@ -1,24 +0,0 @@ -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 - -internal 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, - ) diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/mapper/NovelMapper.kt b/feature/library/src/main/java/com/into/websoso/feature/library/mapper/NovelMapper.kt new file mode 100644 index 000000000..ca1cd5d51 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/mapper/NovelMapper.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.Companion.toAttractivePoints +import com.into.websoso.domain.library.model.NovelRating +import com.into.websoso.domain.library.model.ReadStatus +import com.into.websoso.feature.library.model.NovelUiModel +import com.into.websoso.feature.library.model.ReadStatusUiModel + +internal fun NovelEntity.toUiModel(): NovelUiModel = + NovelUiModel( + novelId = novelId, + title = title, + startDate = startDate, + endDate = endDate, + novelImage = novelImage, + readStatus = ReadStatusUiModel.from(ReadStatus.from(readStatus)), + userNovelRating = NovelRating.from(userNovelRating).takeIf { it.rating.value > 0f }, + novelRating = NovelRating.from(novelRating), + attractivePoints = attractivePoints.toAttractivePoints(), + keywords = keywords, + myFeeds = myFeeds, + isInterest = isInterest, + ) 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 deleted file mode 100644 index 571dd4f85..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/AttractivePointUiModel.kt +++ /dev/null @@ -1,9 +0,0 @@ -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, -) 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 deleted file mode 100644 index b7235a337..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterType.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.into.websoso.feature.library.model - -internal enum class LibraryFilterType { - Interest, - ReadStatus, - Rating, - AttractivePoint, -} diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiModel.kt new file mode 100644 index 000000000..b08213c19 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryFilterUiModel.kt @@ -0,0 +1,42 @@ +package com.into.websoso.feature.library.model + +import com.into.websoso.domain.library.model.AttractivePoints +import com.into.websoso.domain.library.model.NovelRating +import com.into.websoso.domain.library.model.ReadStatuses +import com.into.websoso.domain.library.model.SortCriteria + +data class LibraryFilterUiModel( + val isInterested: Boolean = false, + val sortCriteria: SortCriteria = SortCriteria.RECENT, + val readStatuses: ReadStatuses = ReadStatuses(), + val attractivePoints: AttractivePoints = AttractivePoints(), + val novelRating: NovelRating = NovelRating(), +) { + val ratingText: String + get() = if (novelRating.isSelected) "${novelRating.rating.value}이상" else "별점" + + val readStatusLabelText: String + get() = createLabel( + values = readStatuses.selectedLabels, + labelTitle = "읽기 상태", + ) + + val attractivePointLabelText: String + get() = createLabel( + values = attractivePoints.selectedLabels, + labelTitle = "매력 포인트", + ) + + val isFilterApplied: Boolean + get() = readStatuses.isSelected || attractivePoints.isSelected || novelRating.isSelected || isInterested + + private fun createLabel( + labelTitle: String, + values: List, + ): String = + when { + values.isEmpty() -> labelTitle + values.size == 1 -> values.first() + else -> "${values.first()} 외 ${values.size - 1}" + } +} 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 deleted file mode 100644 index 7f923ac2e..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/LibraryListItemModel.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.into.websoso.feature.library.model - -import com.into.websoso.core.common.extensions.formatDateRange - -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, - 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, -} 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 index 0ff1edb0b..627e78712 100644 --- 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 @@ -1,65 +1,7 @@ package com.into.websoso.feature.library.model -import com.into.websoso.domain.library.model.AttractivePoints -import com.into.websoso.domain.library.model.ReadStatus - data class LibraryUiState( val isGrid: Boolean = true, - val libraryFilterUiState: LibraryFilterUiState = LibraryFilterUiState(), + val libraryFilterUiModel: LibraryFilterUiModel = LibraryFilterUiModel(), ) -data class LibraryFilterUiState( - val selectedSortType: SortTypeUiModel = SortTypeUiModel.RECENT, - val isInterested: Boolean = false, - val readStatuses: Map = mapOf( - ReadStatus.WATCHING to false, - ReadStatus.WATCHED to false, - ReadStatus.QUIT to false, - ), - val attractivePoints: Map = mapOf( - AttractivePoints.VIBE to false, - AttractivePoints.WORLDVIEW to false, - AttractivePoints.CHARACTER to false, - AttractivePoints.MATERIAL to false, - AttractivePoints.RELATIONSHIP to false, - ), - val novelRating: Float = 0f, -) { - val isRatingSelected: Boolean = novelRating != 0f - val ratingText: String - get() = if (isRatingSelected) "${novelRating}이상" else "별점" - - val readStatusLabelText: String - get() = buildLabel( - readStatuses.filterValues { it }.keys.map { status -> - ReadStatusUiModel.valueOf(status.name).label - }, - "읽기 상태", - ) - - val attractivePointLabelText: String - get() = buildLabel( - attractivePoints.filterValues { it }.keys.map { point -> - point.label - }, - "매력 포인트", - ) - - val isFilterApplied: Boolean - get() = readStatuses.values.any { it } || - attractivePoints.values.any { it } || - isRatingSelected || - isInterested - - companion object { - fun buildLabel( - values: List, - labelTitle: String, - ): String = - when { - values.isEmpty() -> labelTitle - values.size == 1 -> values.first() - else -> "${values.first()} 외 ${values.size - 1}개" - } - } -} diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt new file mode 100644 index 000000000..180401071 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/NovelUiModel.kt @@ -0,0 +1,37 @@ +package com.into.websoso.feature.library.model + +import com.into.websoso.core.common.extensions.formatDateRange +import com.into.websoso.domain.library.model.AttractivePoints +import com.into.websoso.domain.library.model.NovelRating + +internal data class NovelUiModel( + val novelId: Long, + val title: String, + val startDate: String?, + val endDate: String?, + val novelImage: String, + val readStatus: ReadStatusUiModel?, + val userNovelRating: NovelRating?, + val novelRating: NovelRating, + val attractivePoints: AttractivePoints, + val keywords: List, + val myFeeds: List, + val isInterest: Boolean, + val formattedDateRange: String? = formatDateRange(startDate, endDate), +) { + val ratingStars: List = calculateRatingStars() + + private fun calculateRatingStars(): List { + if (userNovelRating == null) return emptyList() + + val fullStar = userNovelRating.rating.value.toInt() + val halfStar = (userNovelRating.rating.value - fullStar) >= 0.5f + val emptyStar = 5 - fullStar - if (halfStar) 1 else 0 + + return buildList { + repeat(fullStar) { add(RatingStarUiModel.FULL) } + if (halfStar) add(RatingStarUiModel.HALF) + repeat(emptyStar) { add(RatingStarUiModel.EMPTY) } + } + } +} diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/RatingLevelUiModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/RatingLevelUiModel.kt deleted file mode 100644 index 12c3396b3..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/RatingLevelUiModel.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.into.websoso.feature.library.model - -enum class RatingLevelUiModel( - val value: Float, -) { - THREE_POINT_FIVE(3.5f), - FOUR(4.0f), - FOUR_POINT_FIVE(4.5f), - FOUR_POINT_EIGHT(4.8f), -} diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt new file mode 100644 index 000000000..b65d6f320 --- /dev/null +++ b/feature/library/src/main/java/com/into/websoso/feature/library/model/RatingStarUiModel.kt @@ -0,0 +1,7 @@ +package com.into.websoso.feature.library.model + +internal enum class RatingStarUiModel { + FULL, + HALF, + EMPTY, +} 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 index a351b6889..d5bde34fb 100644 --- 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 @@ -4,18 +4,18 @@ 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 +import com.into.websoso.domain.library.model.ReadStatus -enum class ReadStatusUiModel( - val label: String, +internal enum class ReadStatusUiModel( + val readStatus: ReadStatus, val backgroundColor: Color, - val key: String, ) { - WATCHING("보는 중", Primary100, "WATCHING"), - WATCHED("봤어요", Black, "WATCHED"), - QUIT("하차", Gray200, "QUIT"), + WATCHING(ReadStatus.WATCHING, Primary100), + WATCHED(ReadStatus.WATCHED, Black), + QUIT(ReadStatus.QUIT, Gray200), ; companion object { - fun from(value: String?): ReadStatusUiModel? = entries.find { it.key == value } + fun from(readStatus: ReadStatus?): ReadStatusUiModel? = entries.find { it.readStatus == readStatus } } } 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 deleted file mode 100644 index e6e5af4e4..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/model/SortTypeUiModel.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.into.websoso.feature.library.model - -enum class SortTypeUiModel( - val displayName: String, -) { - RECENT("최신 순"), - OLD("오래된 순"), - ; - - companion object { - fun valueOf(sortType: String): SortTypeUiModel = entries.firstOrNull { it.name == sortType } ?: RECENT - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0949a8a76..a98aa41fb 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.6" # Testing Libraries junit = "4.13.2" @@ -51,9 +52,6 @@ coroutines = "1.6.4" coil = "2.7.0" coil-transformers = "1.0.6" -# Paging3 -paging = "3.3.6" - # Misc UI Libraries dots-indicator = "5.0" lottie = "5.0.2" @@ -101,6 +99,9 @@ security-crypto = { module = "androidx.security:security-crypto", version.ref = room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +room-paging = { module = "androidx.room:room-paging", version.ref = "room" } +paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging" } +paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging" } # Testing Libraries junit = { module = "junit:junit", version.ref = "junit" } @@ -151,11 +152,6 @@ firebase-messaging = { module = "com.google.firebase:firebase-messaging" } # Amplitude Libraries amplitude = { module = "com.amplitude:analytics-android", version.ref = "amplitude" } -# Paging3 -paging-runtime = { group = "androidx.paging", name = "paging-runtime", version = "paging" } -paging-common = { group = "androidx.paging", name = "paging-common", version = "paging" } -paging-compose = { group = "androidx.paging", name = "paging-compose", version = "paging" } - # Jetpack Compose Libraries compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" } compose-ui-android = { module = "androidx.compose.ui:ui-android", version.ref = "compose-ui" }