diff --git a/app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt b/app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt index 0ba267bd1..bb6448099 100644 --- a/app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt +++ b/app/src/main/java/com/into/websoso/core/common/util/navigator/WebsosoNavigator.kt @@ -46,8 +46,11 @@ internal class WebsosoNavigator startActivity(intent) } - override fun navigateToUserStorageActivity(startActivity: (Intent) -> Unit) { - val intent = UserStorageActivity.getIntent(context) + override fun navigateToUserStorageActivity( + startActivity: (Intent) -> Unit, + userId: Long, + ) { + val intent = UserStorageActivity.getIntent(context, userId) startActivity(intent) } diff --git a/app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt b/app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt index fc800ae3d..6d38f6370 100644 --- a/app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt +++ b/app/src/main/java/com/into/websoso/ui/otherUserPage/otherUserLibrary/OtherUserLibraryFragment.kt @@ -40,6 +40,7 @@ class OtherUserLibraryFragment : BaseFragment(f RestGenrePreferenceAdapter() } private val singleEventHandler: SingleEventHandler by lazy { SingleEventHandler.from() } + private val userId by lazy { arguments?.getLong(USER_ID_KEY) ?: 0L } override fun onViewCreated( view: View, @@ -63,7 +64,6 @@ class OtherUserLibraryFragment : BaseFragment(f } private fun updateUserId() { - val userId = arguments?.getLong(USER_ID_KEY) ?: 0L otherUserLibraryViewModel.updateUserId(userId) } @@ -222,7 +222,7 @@ class OtherUserLibraryFragment : BaseFragment(f } private fun navigateToUserStorageActivity() { - websosoNavigator.navigateToUserStorageActivity(::startActivity) + websosoNavigator.navigateToUserStorageActivity(::startActivity, userId) } override fun onResume() { diff --git a/app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt b/app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt index 2ed8ddade..dc1ea1885 100644 --- a/app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt +++ b/app/src/main/java/com/into/websoso/ui/userStorage/UserStorageActivity.kt @@ -41,6 +41,13 @@ class UserStorageActivity : AppCompatActivity(R.layout.activity_storage) { } companion object { - fun getIntent(context: Context) = Intent(context, UserStorageActivity::class.java) + private const val USER_ID = "USER_ID" + + fun getIntent( + context: Context, + userId: Long, + ) = Intent(context, UserStorageActivity::class.java).apply { + putExtra(USER_ID, userId) + } } } diff --git a/core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/di/KakaoAuthClientModule.kt b/core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/di/KakaoAuthClientModule.kt index f5f3db1ba..f04aa97fd 100644 --- a/core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/di/KakaoAuthClientModule.kt +++ b/core/auth-kakao/src/main/java/com/into/websoso/core/auth_kakao/di/KakaoAuthClientModule.kt @@ -8,12 +8,14 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.scopes.ActivityScoped import dagger.multibindings.IntoMap @Module @InstallIn(ActivityComponent::class) internal interface KakaoAuthClientModule { @Binds + @ActivityScoped @IntoMap @AuthPlatformKey(AuthPlatform.KAKAO) fun bindKakaoAuthClient(kakaoAuthClient: KakaoAuthClient): AuthClient diff --git a/core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt b/core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt index dc16ee923..af4ce8e35 100644 --- a/core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt +++ b/core/common/src/main/java/com/into/websoso/core/common/navigator/NavigatorProvider.kt @@ -15,7 +15,10 @@ interface NavigatorProvider { fun navigateToOnboardingActivity(startActivity: (Intent) -> Unit) - fun navigateToUserStorageActivity(startActivity: (Intent) -> Unit) + fun navigateToUserStorageActivity( + startActivity: (Intent) -> Unit, + userId: Long, + ) fun navigateToNovelDetailActivity( novelId: Long, 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 index 3da9348a3..50aa44b6a 100644 --- 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 @@ -23,6 +23,9 @@ interface FilteredNovelDao { @Query("DELETE FROM filtered_novels") suspend fun clearAll() + + @Query("SELECT COUNT(*) FROM filtered_novels") + suspend fun selectNovelsCount(): Int } @Module diff --git a/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt b/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt similarity index 65% rename from core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt rename to core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt index b511dc6d9..6e34a676c 100644 --- a/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultMyLibraryFilterDataSource.kt +++ b/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/DefaultLibraryFilterDataSource.kt @@ -6,11 +6,12 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import com.into.websoso.core.common.dispatchers.Dispatcher import com.into.websoso.core.common.dispatchers.WebsosoDispatchers +import com.into.websoso.core.datastore.datasource.library.mapper.toData +import com.into.websoso.core.datastore.datasource.library.mapper.toPreferences import com.into.websoso.core.datastore.datasource.library.model.LibraryFilterPreferences -import com.into.websoso.core.datastore.datasource.library.model.toPreferences -import com.into.websoso.core.datastore.di.MyLibraryFilterDataStore -import com.into.websoso.data.library.datasource.MyLibraryFilterLocalDataSource -import com.into.websoso.data.library.model.LibraryFilterParams +import com.into.websoso.core.datastore.di.LibraryFilterDataStore +import com.into.websoso.data.filter.datasource.LibraryFilterLocalDataSource +import com.into.websoso.data.filter.model.LibraryFilter import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -25,14 +26,14 @@ import kotlinx.serialization.json.Json import javax.inject.Inject import javax.inject.Singleton -internal class DefaultMyLibraryFilterDataSource +internal class DefaultLibraryFilterDataSource @Inject constructor( - @MyLibraryFilterDataStore private val myLibraryFilterDataStore: DataStore, + @LibraryFilterDataStore private val libraryFilterDataStore: DataStore, @Dispatcher(WebsosoDispatchers.DEFAULT) private val dispatcher: CoroutineDispatcher, - ) : MyLibraryFilterLocalDataSource { - override val myLibraryFilterFlow: Flow - get() = myLibraryFilterDataStore.data + ) : LibraryFilterLocalDataSource { + override val libraryFilterFlow: Flow + get() = libraryFilterDataStore.data .map { prefs -> prefs[LIBRARY_FILTER_PARAMS_KEY]?.let { jsonString -> withContext(dispatcher) { @@ -41,20 +42,20 @@ internal class DefaultMyLibraryFilterDataSource } }.distinctUntilChanged() - override suspend fun updateMyLibraryFilter(params: LibraryFilterParams) { + override suspend fun updateLibraryFilter(params: LibraryFilter) { val encodedJsonString = withContext(dispatcher) { Json.encodeToString( params.toPreferences(), ) } - myLibraryFilterDataStore.edit { prefs -> + libraryFilterDataStore.edit { prefs -> prefs[LIBRARY_FILTER_PARAMS_KEY] = encodedJsonString } } - override suspend fun deleteMyLibraryFilter() { - myLibraryFilterDataStore.edit { prefs -> + override suspend fun deleteLibraryFilter() { + libraryFilterDataStore.edit { prefs -> prefs.remove(LIBRARY_FILTER_PARAMS_KEY) } } @@ -66,10 +67,8 @@ internal class DefaultMyLibraryFilterDataSource @Module @InstallIn(SingletonComponent::class) -internal interface MyLibraryFilterDataSourceModule { +internal interface LibraryFilterDataSourceModule { @Binds @Singleton - fun bindMyLibraryFilterLocalDataSource( - defaultMyLibraryFilterDataSource: DefaultMyLibraryFilterDataSource, - ): MyLibraryFilterLocalDataSource + fun bindLibraryFilterLocalDataSource(defaultLibraryFilterDataSource: DefaultLibraryFilterDataSource): LibraryFilterLocalDataSource } diff --git a/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/mapper/LibraryFilterMapper.kt b/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/mapper/LibraryFilterMapper.kt new file mode 100644 index 000000000..08afc26f5 --- /dev/null +++ b/core/datastore/src/main/java/com/into/websoso/core/datastore/datasource/library/mapper/LibraryFilterMapper.kt @@ -0,0 +1,22 @@ +package com.into.websoso.core.datastore.datasource.library.mapper + +import com.into.websoso.core.datastore.datasource.library.model.LibraryFilterPreferences +import com.into.websoso.data.filter.model.LibraryFilter + +internal fun LibraryFilter.toPreferences(): LibraryFilterPreferences = + LibraryFilterPreferences( + sortCriteria = sortCriteria, + isInterested = isInterested, + readStatuses = readStatuses, + attractivePoints = attractivePoints, + novelRating = novelRating, + ) + +internal fun LibraryFilterPreferences.toData(): LibraryFilter = + LibraryFilter( + sortCriteria = sortCriteria, + isInterested = isInterested, + readStatuses = readStatuses, + attractivePoints = attractivePoints, + novelRating = novelRating, + ) 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 4779a99b1..c02114a6a 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 @@ -1,6 +1,5 @@ package com.into.websoso.core.datastore.datasource.library.model -import com.into.websoso.data.library.model.LibraryFilterParams import kotlinx.serialization.Serializable @Serializable @@ -10,22 +9,4 @@ internal data class LibraryFilterPreferences( val readStatuses: Map, val attractivePoints: Map, val novelRating: Float, -) { - internal fun toData(): LibraryFilterParams = - LibraryFilterParams( - sortCriteria = sortCriteria, - isInterested = isInterested, - readStatuses = readStatuses, - attractivePoints = attractivePoints, - novelRating = novelRating, - ) -} - -internal fun LibraryFilterParams.toPreferences(): LibraryFilterPreferences = - LibraryFilterPreferences( - sortCriteria = sortCriteria, - isInterested = isInterested, - readStatuses = readStatuses, - attractivePoints = attractivePoints, - novelRating = novelRating, - ) +) diff --git a/core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt b/core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt index 9b9c95579..51388dd81 100644 --- a/core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt +++ b/core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreModule.kt @@ -17,9 +17,9 @@ internal object DataStoreModule { private const val ACCOUNT_DATASTORE = "ACCOUNT_DATASTORE" private val Context.accountDataStore: DataStore by preferencesDataStore(name = ACCOUNT_DATASTORE) - private const val MY_LIBRARY_FILTER_DATASTORE = "MY_LIBRARY_FILTER_DATASTORE" - private val Context.myLibraryFilterDataStore: DataStore by preferencesDataStore( - name = MY_LIBRARY_FILTER_DATASTORE, + private const val LIBRARY_FILTER_DATASTORE = "LIBRARY_FILTER_DATASTORE" + private val Context.libraryFilterDataStore: DataStore by preferencesDataStore( + name = LIBRARY_FILTER_DATASTORE, ) @Provides @@ -31,8 +31,8 @@ internal object DataStoreModule { @Provides @Singleton - @MyLibraryFilterDataStore - internal fun provideMyLibraryFilterPreferencesDataStore( + @LibraryFilterDataStore + internal fun provideLibraryFilterPreferencesDataStore( @ApplicationContext context: Context, - ): DataStore = context.myLibraryFilterDataStore + ): DataStore = context.libraryFilterDataStore } diff --git a/core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt b/core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt index 6f91a5ede..1499bb5a4 100644 --- a/core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt +++ b/core/datastore/src/main/java/com/into/websoso/core/datastore/di/DataStoreQualifier.kt @@ -8,4 +8,4 @@ internal annotation class AccountDataStore @Qualifier @Retention(AnnotationRetention.BINARY) -internal annotation class MyLibraryFilterDataStore +internal annotation class LibraryFilterDataStore 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 new file mode 100644 index 000000000..b6b759d9f --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/filter/FilterRepository.kt @@ -0,0 +1,16 @@ +package com.into.websoso.data.filter + +import com.into.websoso.data.filter.model.LibraryFilter +import kotlinx.coroutines.flow.Flow + +interface FilterRepository { + val filterFlow: Flow + + suspend fun updateFilter( + readStatuses: Map? = null, + attractivePoints: Map? = 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 new file mode 100644 index 000000000..cf4a714ae --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/filter/datasource/LibraryFilterLocalDataSource.kt @@ -0,0 +1,12 @@ +package com.into.websoso.data.filter.datasource + +import com.into.websoso.data.filter.model.LibraryFilter +import kotlinx.coroutines.flow.Flow + +interface LibraryFilterLocalDataSource { + val libraryFilterFlow: Flow + + suspend fun updateLibraryFilter(params: LibraryFilter) + + suspend fun deleteLibraryFilter() +} diff --git a/data/library/src/main/java/com/into/websoso/data/filter/di/FilterRepositoryModule.kt b/data/library/src/main/java/com/into/websoso/data/filter/di/FilterRepositoryModule.kt new file mode 100644 index 000000000..2f44f59b6 --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/filter/di/FilterRepositoryModule.kt @@ -0,0 +1,27 @@ +package com.into.websoso.data.filter.di + +import androidx.lifecycle.SavedStateHandle +import com.into.websoso.data.filter.FilterRepository +import com.into.websoso.data.filter.repository.MyLibraryFilterRepository +import com.into.websoso.data.filter.repository.UserLibraryFilterRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped +import javax.inject.Provider + +@Module +@InstallIn(ViewModelComponent::class) +internal object FilterRepositoryModule { + @Provides + @ViewModelScoped + fun provideFilterRepository( + savedStateHandle: SavedStateHandle, + myFilterRepository: Provider, + userFilterRepository: Provider, + ): FilterRepository { + val userId: Long? = savedStateHandle["USER_ID"] + return if (userId == null) myFilterRepository.get() else userFilterRepository.get() + } +} 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 new file mode 100644 index 000000000..dbfcbf270 --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/filter/model/LibraryFilter.kt @@ -0,0 +1,24 @@ +package com.into.websoso.data.filter.model + +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 + } +} 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 new file mode 100644 index 000000000..621daa5e7 --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/filter/repository/MyLibraryFilterRepository.kt @@ -0,0 +1,40 @@ +package com.into.websoso.data.filter.repository + +import com.into.websoso.data.filter.FilterRepository +import com.into.websoso.data.filter.datasource.LibraryFilterLocalDataSource +import com.into.websoso.data.filter.model.LibraryFilter +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +internal class MyLibraryFilterRepository + @Inject + constructor( + private val myLibraryFilterLocalDataSource: LibraryFilterLocalDataSource, + ) : FilterRepository { + override val filterFlow: Flow = + myLibraryFilterLocalDataSource.libraryFilterFlow + .map { it ?: LibraryFilter() } + .distinctUntilChanged() + + override suspend fun updateFilter( + readStatuses: Map?, + attractivePoints: Map?, + novelRating: Float?, + isInterested: Boolean?, + sortCriteria: String?, + ) { + val savedFilter = filterFlow.first() + val updatedFilter = savedFilter.copy( + sortCriteria = sortCriteria ?: savedFilter.sortCriteria, + isInterested = isInterested ?: savedFilter.isInterested, + readStatuses = readStatuses ?: savedFilter.readStatuses, + attractivePoints = attractivePoints ?: savedFilter.attractivePoints, + novelRating = novelRating ?: savedFilter.novelRating, + ) + + myLibraryFilterLocalDataSource.updateLibraryFilter(params = 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 new file mode 100644 index 000000000..03f67c513 --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/filter/repository/UserLibraryFilterRepository.kt @@ -0,0 +1,36 @@ +package com.into.websoso.data.filter.repository + +import com.into.websoso.data.filter.FilterRepository +import com.into.websoso.data.filter.model.LibraryFilter +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +internal class UserLibraryFilterRepository + @Inject + constructor() : FilterRepository { + private val _filterFlow = MutableStateFlow(LibraryFilter()) + override val filterFlow: Flow = _filterFlow.asStateFlow() + + override suspend fun updateFilter( + readStatuses: Map?, + attractivePoints: Map?, + novelRating: Float?, + isInterested: Boolean?, + sortCriteria: String?, + ) { + val savedFilter = filterFlow.first() + val updatedFilter = savedFilter.copy( + sortCriteria = sortCriteria ?: savedFilter.sortCriteria, + isInterested = isInterested ?: savedFilter.isInterested, + readStatuses = readStatuses ?: savedFilter.readStatuses, + attractivePoints = attractivePoints ?: savedFilter.attractivePoints, + novelRating = novelRating ?: savedFilter.novelRating, + ) + + _filterFlow.update { updatedFilter } + } + } diff --git a/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt b/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt index 855cfeb5c..f32964904 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/LibraryRepository.kt @@ -1,99 +1,13 @@ package com.into.websoso.data.library -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.database.entity.InDatabaseNovelEntity -import com.into.websoso.data.account.AccountRepository -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.datasource.MyLibraryFilterLocalDataSource -import com.into.websoso.data.library.mediator.FilteredNovelRemoteMediator -import com.into.websoso.data.library.mediator.NovelRemoteMediator -import com.into.websoso.data.library.model.LibraryFilterParams import com.into.websoso.data.library.model.NovelEntity -import com.into.websoso.data.library.model.toData import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.map -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class LibraryRepository - @Inject - constructor( - private val accountRepository: AccountRepository, - private val libraryRemoteDataSource: LibraryRemoteDataSource, - private val libraryLocalDataSource: LibraryLocalDataSource, - private val filteredLibraryLocalDataSource: FilteredLibraryLocalDataSource, - private val myLibraryFilterLocalDataSource: MyLibraryFilterLocalDataSource, - ) { - val myLibraryFilter = myLibraryFilterLocalDataSource.myLibraryFilterFlow +interface LibraryRepository { + val libraryFlow: Flow> - @OptIn(ExperimentalPagingApi::class) - fun getLibrary(): Flow> = - Pager( - config = PagingConfig(pageSize = PAGE_SIZE), - remoteMediator = NovelRemoteMediator( - userId = accountRepository.userId, - libraryLocalDataSource = libraryLocalDataSource, - libraryRemoteDataSource = libraryRemoteDataSource, - ), - pagingSourceFactory = libraryLocalDataSource::selectAllNovels, - ).flow.map { pagingData -> - pagingData.map(InDatabaseNovelEntity::toData) - } - - @OptIn(ExperimentalPagingApi::class) - fun getFilteredLibrary( - readStatuses: List, - attractivePoints: List, - isInterested: Boolean, - novelRating: Float, - sortCriteria: String, - ): Flow> = - Pager( - config = PagingConfig(pageSize = PAGE_SIZE), - remoteMediator = FilteredNovelRemoteMediator( - userId = accountRepository.userId, - libraryRemoteDataSource = libraryRemoteDataSource, - filteredLibraryLocalDataSource = filteredLibraryLocalDataSource, - isInterested = isInterested, - readStatuses = readStatuses, - attractivePoints = attractivePoints, - novelRating = novelRating, - sortCriteria = sortCriteria, - ), - pagingSourceFactory = filteredLibraryLocalDataSource::selectAllNovels, - ).flow.map { pagingData -> - pagingData.map(InDatabaseFilteredNovelEntity::toData) - } - - suspend fun updateMyLibraryFilter( - readStatuses: Map? = null, - attractivePoints: Map? = null, - novelRating: Float? = null, - isInterested: Boolean? = null, - sortCriteria: String? = null, - ) { - val savedFilter = myLibraryFilter.firstOrNull() ?: LibraryFilterParams() - val updatedFilter = savedFilter.copy( - sortCriteria = sortCriteria ?: savedFilter.sortCriteria, - isInterested = isInterested ?: savedFilter.isInterested, - readStatuses = readStatuses ?: savedFilter.readStatuses, - attractivePoints = attractivePoints ?: savedFilter.attractivePoints, - novelRating = novelRating ?: savedFilter.novelRating, - ) - - myLibraryFilterLocalDataSource.updateMyLibraryFilter(params = updatedFilter) - } - - companion object { - private const val PAGE_SIZE = 10 - } + companion object { + const val PAGE_SIZE = 30 } +} 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 index a3c77a720..99c57b970 100644 --- 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 @@ -25,9 +25,10 @@ internal class DefaultFilteredLibraryLocalDataSource private val filteredNovelDao: FilteredNovelDao, ) : FilteredLibraryLocalDataSource { override suspend fun insertNovels(novels: List) { + val offset = filteredNovelDao.selectNovelsCount() filteredNovelDao.insertFilteredNovels( novels.mapIndexed { index, novelEntity -> - novelEntity.toFilteredNovelDatabase(index) + novelEntity.toFilteredNovelDatabase(offset + index) }, ) } diff --git a/data/library/src/main/java/com/into/websoso/data/library/datasource/MyLibraryFilterLocalDataSource.kt b/data/library/src/main/java/com/into/websoso/data/library/datasource/MyLibraryFilterLocalDataSource.kt deleted file mode 100644 index b4cf5ed13..000000000 --- a/data/library/src/main/java/com/into/websoso/data/library/datasource/MyLibraryFilterLocalDataSource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.into.websoso.data.library.datasource - -import com.into.websoso.data.library.model.LibraryFilterParams -import kotlinx.coroutines.flow.Flow - -interface MyLibraryFilterLocalDataSource { - val myLibraryFilterFlow: Flow - - suspend fun updateMyLibraryFilter(params: LibraryFilterParams) - - suspend fun deleteMyLibraryFilter() -} diff --git a/data/library/src/main/java/com/into/websoso/data/library/di/LibraryRepositoryModule.kt b/data/library/src/main/java/com/into/websoso/data/library/di/LibraryRepositoryModule.kt new file mode 100644 index 000000000..d203ef1cf --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/library/di/LibraryRepositoryModule.kt @@ -0,0 +1,27 @@ +package com.into.websoso.data.library.di + +import androidx.lifecycle.SavedStateHandle +import com.into.websoso.data.library.LibraryRepository +import com.into.websoso.data.library.repository.MyLibraryRepository +import com.into.websoso.data.library.repository.UserLibraryRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped +import javax.inject.Provider + +@Module +@InstallIn(ViewModelComponent::class) +internal object LibraryRepositoryModule { + @Provides + @ViewModelScoped + fun provideLibraryRepository( + savedStateHandle: SavedStateHandle, + myLibraryRepository: Provider, + userLibraryRepository: Provider + ): LibraryRepository { + val userId: Long? = savedStateHandle["USER_ID"] + return if (userId == null) myLibraryRepository.get() else userLibraryRepository.get().create(userId) + } +} diff --git a/data/library/src/main/java/com/into/websoso/data/library/mediator/NovelRemoteMediator.kt b/data/library/src/main/java/com/into/websoso/data/library/mediator/NovelRemoteMediator.kt deleted file mode 100644 index 825efe03b..000000000 --- a/data/library/src/main/java/com/into/websoso/data/library/mediator/NovelRemoteMediator.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.into.websoso.data.library.mediator - -import androidx.paging.ExperimentalPagingApi -import androidx.paging.LoadType -import androidx.paging.PagingState -import androidx.paging.RemoteMediator -import com.into.websoso.core.database.entity.InDatabaseNovelEntity -import com.into.websoso.data.library.datasource.LibraryLocalDataSource -import com.into.websoso.data.library.datasource.LibraryRemoteDataSource - -@OptIn(ExperimentalPagingApi::class) -class NovelRemoteMediator( - private val userId: Long, - private val libraryRemoteDataSource: LibraryRemoteDataSource, - private val libraryLocalDataSource: LibraryLocalDataSource, -) : RemoteMediator() { - override suspend fun load( - loadType: LoadType, - state: PagingState, - ): MediatorResult { - val lastUserNovelId = when (loadType) { - LoadType.REFRESH -> null - LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) - LoadType.APPEND -> { - val lastItem = state.lastItemOrNull() - lastItem?.userNovelId ?: return MediatorResult.Success(true) - } - } ?: DEFAULT_LAST_USER_NOVEL_ID - - return try { - val response = libraryRemoteDataSource.getUserNovels( - userId = userId, - lastUserNovelId = lastUserNovelId, - size = state.config.pageSize, - sortCriteria = DEFAULT_SORT_CRITERIA, - isInterest = null, - readStatuses = null, - attractivePoints = null, - novelRating = null, - query = null, - updatedSince = null, - ) - if (loadType == LoadType.REFRESH) { - libraryLocalDataSource.deleteAllNovels() - } - libraryLocalDataSource.insertNovels(response.userNovels) - MediatorResult.Success(endOfPaginationReached = !response.isLoadable) - } catch (e: Exception) { - MediatorResult.Error(e) - } - } - - companion object { - private const val DEFAULT_LAST_USER_NOVEL_ID = 0L - private const val DEFAULT_SORT_CRITERIA = "RECENT" - } -} diff --git a/data/library/src/main/java/com/into/websoso/data/library/model/LibraryFilterParams.kt b/data/library/src/main/java/com/into/websoso/data/library/model/LibraryFilterParams.kt deleted file mode 100644 index 1960b61bd..000000000 --- a/data/library/src/main/java/com/into/websoso/data/library/model/LibraryFilterParams.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.into.websoso.data.library.model - -data class LibraryFilterParams( - 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(), -) - -private const val DEFAULT_SORT_CRITERIA = "RECENT" -private const val DEFAULT_IS_INTERESTED = false -private const val DEFAULT_NOVEL_RATING = 0.0f diff --git a/data/library/src/main/java/com/into/websoso/data/library/model/LibraryReadStatus.kt b/data/library/src/main/java/com/into/websoso/data/library/model/LibraryReadStatus.kt deleted file mode 100644 index 449b592e1..000000000 --- a/data/library/src/main/java/com/into/websoso/data/library/model/LibraryReadStatus.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.into.websoso.data.library.model - -internal enum class LibraryReadStatus { - Interest, - ReadStatus, - Rating, - AttractivePoint, -} 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 new file mode 100644 index 000000000..f3a5db86b --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryPagingSource.kt @@ -0,0 +1,56 @@ +package com.into.websoso.data.library.paging + +import androidx.paging.PagingSource +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 + +class LibraryPagingSource( + private val userId: Long, + private val libraryRemoteDataSource: LibraryRemoteDataSource, + private val filterParams: LibraryFilter, +) : 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, + ) + + 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) + } + } + + override fun getRefreshKey(state: PagingState): Long? = + state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.userNovelId + } + + companion object { + private const val DEFAULT_LAST_USER_NOVEL_ID = 0L + } +} diff --git a/data/library/src/main/java/com/into/websoso/data/library/mediator/FilteredNovelRemoteMediator.kt b/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt similarity index 75% rename from data/library/src/main/java/com/into/websoso/data/library/mediator/FilteredNovelRemoteMediator.kt rename to data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt index 491f158b8..df343a219 100644 --- a/data/library/src/main/java/com/into/websoso/data/library/mediator/FilteredNovelRemoteMediator.kt +++ b/data/library/src/main/java/com/into/websoso/data/library/paging/LibraryRemoteMediator.kt @@ -1,4 +1,4 @@ -package com.into.websoso.data.library.mediator +package com.into.websoso.data.library.paging import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType @@ -11,19 +11,16 @@ 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 @OptIn(ExperimentalPagingApi::class) -class FilteredNovelRemoteMediator( +class LibraryRemoteMediator( private val userId: Long, private val libraryRemoteDataSource: LibraryRemoteDataSource, private val filteredLibraryLocalDataSource: FilteredLibraryLocalDataSource, - private val isInterested: Boolean, - private val sortCriteria: String, - private val readStatuses: List, - private val attractivePoints: List, - private val novelRating: Float, + private val libraryFilter: LibraryFilter, ) : RemoteMediator() { override suspend fun load( loadType: LoadType, @@ -40,11 +37,15 @@ class FilteredNovelRemoteMediator( userId = userId, lastUserNovelId = lastUserNovelId, size = state.config.pageSize, - sortCriteria = sortCriteria, - isInterest = if (!isInterested) null else true, - readStatuses = readStatuses.ifEmpty { null }, - attractivePoints = attractivePoints.ifEmpty { null }, - novelRating = if (novelRating.isCloseTo(DEFAULT_NOVEL_RATING)) null else novelRating, + 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, ) 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 new file mode 100644 index 000000000..b6ac84ef9 --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/library/repository/MyLibraryRepository.kt @@ -0,0 +1,49 @@ +package com.into.websoso.data.library.repository + +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.data.account.AccountRepository +import com.into.websoso.data.filter.FilterRepository +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.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 + @Inject + constructor( + filterRepository: FilterRepository, + private val accountRepository: AccountRepository, + private val libraryRemoteDataSource: LibraryRemoteDataSource, + private val filteredLibraryLocalDataSource: FilteredLibraryLocalDataSource, + ) : LibraryRepository { + @OptIn(ExperimentalPagingApi::class, ExperimentalCoroutinesApi::class) + override val libraryFlow: Flow> = + filterRepository.filterFlow + .flatMapLatest { filter -> + Pager( + config = PagingConfig(pageSize = PAGE_SIZE), + remoteMediator = LibraryRemoteMediator( + userId = accountRepository.userId, + libraryRemoteDataSource = libraryRemoteDataSource, + filteredLibraryLocalDataSource = filteredLibraryLocalDataSource, + libraryFilter = filter, + ), + pagingSourceFactory = filteredLibraryLocalDataSource::selectAllNovels, + ).flow.map { pagingData -> + pagingData.map(InDatabaseFilteredNovelEntity::toData) + } + } + } 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 new file mode 100644 index 000000000..5b2fba9cb --- /dev/null +++ b/data/library/src/main/java/com/into/websoso/data/library/repository/UserLibraryRepository.kt @@ -0,0 +1,46 @@ +package com.into.websoso.data.library.repository + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.into.websoso.data.filter.FilterRepository +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 +import com.into.websoso.data.library.model.NovelEntity +import com.into.websoso.data.library.paging.LibraryPagingSource +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest + +internal class UserLibraryRepository + @AssistedInject + constructor( + filterRepository: FilterRepository, + @Assisted private val userId: Long, + private val libraryRemoteDataSource: LibraryRemoteDataSource, + ) : LibraryRepository { + @AssistedFactory + interface Factory { + fun create(userId: Long): UserLibraryRepository + } + + @OptIn(ExperimentalCoroutinesApi::class) + override val libraryFlow: Flow> = + filterRepository.filterFlow + .flatMapLatest { filter -> + Pager( + config = PagingConfig(pageSize = PAGE_SIZE), + pagingSourceFactory = { + LibraryPagingSource( + userId = userId, + libraryRemoteDataSource = libraryRemoteDataSource, + filterParams = filter, + ) + }, + ).flow + } + } diff --git a/domain/library/src/main/java/com/into/websoso/domain/library/GetLibraryUseCase.kt b/domain/library/src/main/java/com/into/websoso/domain/library/GetLibraryUseCase.kt deleted file mode 100644 index 71636acf7..000000000 --- a/domain/library/src/main/java/com/into/websoso/domain/library/GetLibraryUseCase.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.into.websoso.domain.library - -import androidx.paging.PagingData -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.ReadStatus -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -class GetLibraryUseCase - @Inject - constructor( - private val libraryRepository: LibraryRepository, - ) { - operator fun invoke( - readStatuses: Map, - attractivePoints: Map, - sortCriteria: String, - isInterested: Boolean, - novelRating: Float, - ): Flow> { - val isEmptyFilter = readStatuses.values.none { it } && - attractivePoints.values.none { it } && - novelRating == 0.0f && - !isInterested && - sortCriteria == "RECENT" - - return if (isEmptyFilter) { - libraryRepository.getLibrary() - } else { - libraryRepository.getFilteredLibrary( - readStatuses = readStatuses.toSelectedKeyList { it.name }, - attractivePoints = attractivePoints.toSelectedKeyList { it.name }, - novelRating = novelRating, - isInterested = isInterested, - sortCriteria = sortCriteria, - ) - } - } - - private fun Map.toSelectedKeyList(toSelectedKeyName: (K) -> String): List = - this - .filterValues { it } - .keys - .map { toSelectedKeyName(it) } - } 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 2156f61d8..79d26317e 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 @@ -22,7 +22,6 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems @@ -31,6 +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.ReadStatus import com.into.websoso.feature.library.component.LibraryEmptyView import com.into.websoso.feature.library.component.LibraryFilterEmptyView import com.into.websoso.feature.library.component.LibraryFilterTopBar @@ -38,11 +39,12 @@ import com.into.websoso.feature.library.component.LibraryGridList 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.filter.LibraryFilterViewModel 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.LibraryUiState +import com.into.websoso.feature.library.model.RatingLevelUiModel import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -54,11 +56,11 @@ fun LibraryScreen( navigateToNormalExploreActivity: () -> Unit, navigateToNovelDetailActivity: (novelId: Long) -> Unit, libraryViewModel: LibraryViewModel, - libraryFilterViewModel: LibraryFilterViewModel = hiltViewModel(), ) { val scope = rememberCoroutineScope() val uiState by libraryViewModel.uiState.collectAsStateWithLifecycle() - val pagingItems = libraryViewModel.novelPagingData + val filterUiState by libraryViewModel.tempFilterUiState.collectAsStateWithLifecycle() + val novels = libraryViewModel.novels .map { it.map(NovelEntity::toUiModel) } .collectAsLazyPagingItems() val latestEffect by rememberUpdatedState(libraryViewModel.scrollToTopEvent) @@ -79,9 +81,9 @@ fun LibraryScreen( } LibraryScreen( - libraryFilterViewModel = libraryFilterViewModel, - pagingItems = pagingItems, + novels = novels, uiState = uiState, + filterUiState = filterUiState, listState = listState, gridState = gridState, sheetState = bottomSheetState, @@ -98,7 +100,7 @@ fun LibraryScreen( isShowBottomSheet = true bottomSheetState.show() }.invokeOnCompletion { - libraryFilterViewModel.updateMyLibraryFilter(uiState.libraryFilterUiState) + libraryViewModel.updateMyLibraryFilter() } }, onSortClick = libraryViewModel::updateSortType, @@ -107,15 +109,20 @@ fun LibraryScreen( onSearchClick = { /* TODO */ }, onExploreClick = navigateToNormalExploreActivity, onInterestClick = libraryViewModel::updateInterestedNovels, + onAttractivePointClick = libraryViewModel::updateAttractivePoints, + onReadStatusClick = libraryViewModel::updateReadStatus, + onRatingClick = libraryViewModel::updateRating, + onResetClick = libraryViewModel::resetFilter, + onFilterSearchClick = libraryViewModel::searchFilteredNovels, ) } @OptIn(ExperimentalMaterial3Api::class) @Composable private fun LibraryScreen( - libraryFilterViewModel: LibraryFilterViewModel, - pagingItems: LazyPagingItems, + novels: LazyPagingItems, uiState: LibraryUiState, + filterUiState: LibraryFilterUiState, listState: LazyListState, gridState: LazyGridState, sheetState: SheetState, @@ -128,6 +135,11 @@ private fun LibraryScreen( onSearchClick: () -> Unit, onExploreClick: () -> Unit, onInterestClick: () -> Unit, + onAttractivePointClick: (AttractivePoints) -> Unit, + onReadStatusClick: (ReadStatus) -> Unit, + onRatingClick: (rating: RatingLevelUiModel) -> Unit, + onResetClick: () -> Unit, + onFilterSearchClick: () -> Unit, ) { Column( modifier = Modifier @@ -143,7 +155,7 @@ private fun LibraryScreen( LibraryFilterTopBar( libraryFilterUiState = uiState.libraryFilterUiState, - totalCount = pagingItems.itemCount, + totalCount = novels.itemCount, isGrid = uiState.isGrid, onFilterClick = onFilterClick, onSortClick = onSortClick, @@ -154,8 +166,8 @@ private fun LibraryScreen( Spacer(modifier = Modifier.height(4.dp)) when { - pagingItems.itemCount == 0 && - pagingItems.loadState.refresh !is LoadState.Loading -> { + novels.itemCount == 0 && + novels.loadState.refresh !is LoadState.Loading -> { if (uiState.libraryFilterUiState.isFilterApplied) { LibraryFilterEmptyView() } else { @@ -165,7 +177,7 @@ private fun LibraryScreen( uiState.isGrid -> { LibraryGridList( - novels = pagingItems, + novels = novels, gridState = gridState, onItemClick = onItemClick, ) @@ -173,7 +185,7 @@ private fun LibraryScreen( else -> { LibraryList( - novels = pagingItems, + novels = novels, listState = listState, onItemClick = onItemClick, ) @@ -183,9 +195,14 @@ private fun LibraryScreen( if (isShowBottomSheet) { LibraryFilterBottomSheetScreen( + filterUiState = filterUiState, sheetState = sheetState, onDismissRequest = onDismissRequest, - viewModel = libraryFilterViewModel, + onAttractivePointClick = onAttractivePointClick, + onReadStatusClick = onReadStatusClick, + onRatingClick = onRatingClick, + onResetClick = onResetClick, + onFilterSearchClick = onFilterSearchClick, ) } } 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 21382aff5..3ac822731 100644 --- a/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt +++ b/feature/library/src/main/java/com/into/websoso/feature/library/LibraryViewModel.kt @@ -2,25 +2,24 @@ package com.into.websoso.feature.library import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData import androidx.paging.cachedIn +import com.into.websoso.core.common.extensions.isCloseTo +import com.into.websoso.data.filter.FilterRepository import com.into.websoso.data.library.LibraryRepository -import com.into.websoso.domain.library.GetLibraryUseCase +import com.into.websoso.data.library.model.NovelEntity import com.into.websoso.domain.library.model.AttractivePoints import com.into.websoso.domain.library.model.ReadStatus +import com.into.websoso.feature.library.model.LibraryFilterUiState 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.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -30,55 +29,44 @@ import javax.inject.Inject class LibraryViewModel @Inject constructor( - getLibraryUseCase: GetLibraryUseCase, - private val libraryRepository: LibraryRepository, + libraryRepository: LibraryRepository, + private val filterRepository: FilterRepository, ) : ViewModel() { private val _uiState = MutableStateFlow(LibraryUiState()) val uiState: StateFlow = _uiState.asStateFlow() + private val _tempFilterUiState = MutableStateFlow(uiState.value.libraryFilterUiState) + val tempFilterUiState = _tempFilterUiState.asStateFlow() + private val _scrollToTopEvent = Channel(Channel.BUFFERED) val scrollToTopEvent: Flow = _scrollToTopEvent.receiveAsFlow() - @OptIn(ExperimentalCoroutinesApi::class) - val novelPagingData = uiState - .map { it.libraryFilterUiState } - .drop(INITIAL_STATE) - .distinctUntilChanged() - .flatMapLatest { filter -> - getLibraryUseCase( - readStatuses = filter.readStatuses, - attractivePoints = filter.attractivePoints, - novelRating = filter.novelRating, - isInterested = filter.isInterested, - sortCriteria = filter.selectedSortType.name, - ) - }.cachedIn(viewModelScope) + val novels: Flow> = + libraryRepository.libraryFlow.cachedIn(viewModelScope) init { - updateMyLibraryFilter() + updateLibraryFilter() } - private fun updateMyLibraryFilter() { + private fun updateLibraryFilter() { viewModelScope.launch { - libraryRepository.myLibraryFilter.collectLatest { myFilter -> - if (myFilter != null) { - _uiState.update { uiState -> - uiState.copy( - libraryFilterUiState = uiState.libraryFilterUiState.copy( - selectedSortType = SortTypeUiModel.valueOf(myFilter.sortCriteria), - isInterested = myFilter.isInterested, - readStatuses = myFilter.readStatuses - .mapKeys { - ReadStatus.from(it.key) - }.ifEmpty { uiState.libraryFilterUiState.readStatuses }, - attractivePoints = myFilter.attractivePoints - .mapKeys { - AttractivePoints.from(it.key) - }.ifEmpty { uiState.libraryFilterUiState.attractivePoints }, - novelRating = myFilter.novelRating, - ), - ) - } + filterRepository.filterFlow.collect { filter -> + _uiState.update { uiState -> + uiState.copy( + libraryFilterUiState = uiState.libraryFilterUiState.copy( + selectedSortType = SortTypeUiModel.valueOf(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, + ), + ) } } } @@ -90,6 +78,12 @@ class LibraryViewModel } } + fun resetScrollPosition() { + viewModelScope.launch { + _scrollToTopEvent.send(Unit) + } + } + fun updateSortType() { val current = uiState.value.libraryFilterUiState.selectedSortType val newSortType = when (current) { @@ -98,45 +92,69 @@ class LibraryViewModel } viewModelScope.launch { - libraryRepository.updateMyLibraryFilter( + filterRepository.updateFilter( sortCriteria = newSortType.name, ) } - - _uiState.update { uiState -> - uiState.copy( - libraryFilterUiState = uiState.libraryFilterUiState.copy( - selectedSortType = newSortType, - ), - ) - } } fun updateInterestedNovels() { val updatedInterested = !uiState.value.libraryFilterUiState.isInterested viewModelScope.launch { - libraryRepository.updateMyLibraryFilter( + filterRepository.updateFilter( isInterested = updatedInterested, ) } + } + + fun updateMyLibraryFilter() { + _tempFilterUiState.update { + uiState.value.libraryFilterUiState + } + } - _uiState.update { + fun updateReadStatus(readStatus: ReadStatus) { + _tempFilterUiState.update { it.copy( - libraryFilterUiState = uiState.value.libraryFilterUiState.copy( - isInterested = updatedInterested, - ), + readStatuses = it.readStatuses.mapValues { (key, value) -> + if (key == readStatus) !value else value + }, ) } } - fun resetScrollPosition() { - viewModelScope.launch { - _scrollToTopEvent.send(Unit) + fun updateAttractivePoints(attractivePoint: AttractivePoints) { + _tempFilterUiState.update { + it.copy( + attractivePoints = it.attractivePoints.mapValues { (key, value) -> + if (key == attractivePoint) !value else value + }, + ) + } + } + + fun updateRating(rating: RatingLevelUiModel) { + _tempFilterUiState.update { + it.copy( + novelRating = if (it.novelRating.isCloseTo(rating.value)) 0f else rating.value, + ) + } + } + + fun resetFilter() { + _tempFilterUiState.update { + LibraryFilterUiState() } } - companion object { - private const val INITIAL_STATE = 1 + 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, + ) + } } } 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 1618af38a..907f5d355 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 @@ -13,12 +13,9 @@ import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.rememberStandardBottomSheetState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.into.websoso.core.designsystem.theme.Black import com.into.websoso.core.designsystem.theme.WebsosoTheme import com.into.websoso.core.designsystem.theme.White @@ -29,28 +26,32 @@ import com.into.websoso.feature.library.filter.component.LibraryFilterBottomShee 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 @Composable @OptIn(ExperimentalMaterial3Api::class) internal fun LibraryFilterBottomSheetScreen( + filterUiState: LibraryFilterUiState, onDismissRequest: () -> Unit, sheetState: SheetState, - viewModel: LibraryFilterViewModel, + onAttractivePointClick: (AttractivePoints) -> Unit, + onReadStatusClick: (ReadStatus) -> Unit, + onRatingClick: (rating: RatingLevelUiModel) -> Unit, + onResetClick: () -> Unit, + onFilterSearchClick: () -> Unit, ) { - val uiState by viewModel.uiState.collectAsStateWithLifecycle() - LibraryFilterBottomSheetScreen( sheetState = sheetState, - readStatues = uiState.readStatuses, - attractivePoints = uiState.attractivePoints, - selectedRating = uiState.novelRating, + readStatues = filterUiState.readStatuses, + attractivePoints = filterUiState.attractivePoints, + selectedRating = filterUiState.novelRating, onDismissRequest = onDismissRequest, - onAttractivePointClick = viewModel::updateAttractivePoints, - onReadStatusClick = viewModel::updateReadStatus, - onRatingClick = viewModel::updateRating, - onResetClick = viewModel::resetFilter, - onFilterSearchClick = viewModel::searchFilteredNovels, + onAttractivePointClick = onAttractivePointClick, + onReadStatusClick = onReadStatusClick, + onRatingClick = onRatingClick, + onResetClick = onResetClick, + onFilterSearchClick = onFilterSearchClick, ) } @@ -134,12 +135,17 @@ private fun LibraryFilterBottomSheetScreen( private fun LibraryFilterBottomSheetPreview() { WebsosoTheme { LibraryFilterBottomSheetScreen( - viewModel = hiltViewModel(), onDismissRequest = {}, sheetState = rememberStandardBottomSheetState( initialValue = SheetValue.Expanded, skipHiddenState = false, ), + filterUiState = LibraryFilterUiState(), + onAttractivePointClick = { }, + onReadStatusClick = { }, + onRatingClick = { }, + onResetClick = { }, + onFilterSearchClick = { }, ) } } diff --git a/feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterViewModel.kt b/feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterViewModel.kt deleted file mode 100644 index 5e8cee9a1..000000000 --- a/feature/library/src/main/java/com/into/websoso/feature/library/filter/LibraryFilterViewModel.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.into.websoso.feature.library.filter - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.into.websoso.core.common.extensions.isCloseTo -import com.into.websoso.data.library.LibraryRepository -import com.into.websoso.domain.library.model.AttractivePoints -import com.into.websoso.domain.library.model.ReadStatus -import com.into.websoso.feature.library.model.LibraryFilterUiState -import com.into.websoso.feature.library.model.RatingLevelUiModel -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class LibraryFilterViewModel - @Inject - constructor( - private val libraryRepository: LibraryRepository, - ) : ViewModel() { - private val _uiState = MutableStateFlow(LibraryFilterUiState()) - val uiState = _uiState.asStateFlow() - - fun updateMyLibraryFilter(libraryFilterUiState: LibraryFilterUiState) { - viewModelScope.launch { - _uiState.update { - libraryFilterUiState - } - } - } - - fun updateReadStatus(readStatus: ReadStatus) { - _uiState.update { - it.copy( - readStatuses = it.readStatuses.mapValues { (key, value) -> - if (key == readStatus) !value else value - }, - ) - } - } - - fun updateAttractivePoints(attractivePoint: AttractivePoints) { - _uiState.update { - it.copy( - attractivePoints = it.attractivePoints.mapValues { (key, value) -> - if (key == attractivePoint) !value else value - }, - ) - } - } - - fun updateRating(rating: RatingLevelUiModel) { - _uiState.update { - it.copy( - novelRating = if (it.novelRating.isCloseTo(rating.value)) 0f else rating.value, - ) - } - } - - fun resetFilter() { - _uiState.update { - LibraryFilterUiState() - } - } - - fun searchFilteredNovels() { - viewModelScope.launch { - libraryRepository.updateMyLibraryFilter( - readStatuses = uiState.value.readStatuses.mapKeys { it.key.key }, - attractivePoints = uiState.value.attractivePoints.mapKeys { it.key.key }, - novelRating = uiState.value.novelRating, - ) - } - } - }