From 3b4a1b1066a1411b80a48fcec6f721a7b0d4fd83 Mon Sep 17 00:00:00 2001 From: jacobrein Date: Thu, 23 Jan 2025 07:30:51 -0700 Subject: [PATCH] Refactor: Migrate data operations to FavoritesRepository This commit refactors data operations related to favorites and watched chapters by migrating them to a new `FavoritesRepository` class. - Created a new `FavoritesRepository` to centralize data operations for favorites and watched chapters. - Moved favorite and watched chapter-related functions from ViewModels and Fragments to the `FavoritesRepository`. - Updated ViewModels and Fragments to use the `FavoritesRepository` for data operations. - Updated the Koin module to include the `FavoritesRepository`. - Removed direct database and FirebaseDb calls from ViewModels and Fragments. - Updated related dependencies and imports. - Improved code organization and maintainability by centralizing data operations. --- .../uiviews/di/RepositoryModule.kt | 2 + .../uiviews/presentation/all/AllViewModel.kt | 10 ++- .../presentation/details/DetailsViewModel.kt | 60 ++++++++-------- .../favorite/FavoriteViewModel.kt | 17 ++--- .../presentation/recent/RecentViewModel.kt | 10 ++- .../settings/MoreSettingsViewModel.kt | 5 +- .../uiviews/repository/FavoritesRepository.kt | 70 +++++++++++++++++++ .../reader/compose/ReadViewModel.kt | 12 ++-- 8 files changed, 124 insertions(+), 62 deletions(-) create mode 100644 UIViews/src/main/java/com/programmersbox/uiviews/repository/FavoritesRepository.kt diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/di/RepositoryModule.kt b/UIViews/src/main/java/com/programmersbox/uiviews/di/RepositoryModule.kt index 00bc6745..acd4ad72 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/di/RepositoryModule.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/di/RepositoryModule.kt @@ -3,6 +3,7 @@ package com.programmersbox.uiviews.di import com.programmersbox.extensionloader.SourceRepository import com.programmersbox.uiviews.repository.ChangingSettingsRepository import com.programmersbox.uiviews.repository.CurrentSourceRepository +import com.programmersbox.uiviews.repository.FavoritesRepository import com.programmersbox.uiviews.repository.NotificationRepository import org.koin.core.module.Module import org.koin.core.module.dsl.singleOf @@ -12,4 +13,5 @@ fun Module.repository() { single { CurrentSourceRepository() } single { ChangingSettingsRepository() } singleOf(::NotificationRepository) + singleOf(::FavoritesRepository) } \ No newline at end of file diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/all/AllViewModel.kt b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/all/AllViewModel.kt index 491dd624..a76e99fb 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/all/AllViewModel.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/all/AllViewModel.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.toMutableStateList -import androidx.compose.ui.util.fastMaxBy import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.programmersbox.favoritesdatabase.DbModel @@ -13,12 +12,12 @@ import com.programmersbox.favoritesdatabase.ItemDao import com.programmersbox.models.ApiService import com.programmersbox.models.ItemModel import com.programmersbox.uiviews.repository.CurrentSourceRepository +import com.programmersbox.uiviews.repository.FavoritesRepository import com.programmersbox.uiviews.utils.DefaultToastItems import com.programmersbox.uiviews.utils.ToastItems import com.programmersbox.uiviews.utils.dispatchIoAndCatchList import com.programmersbox.uiviews.utils.fireListener import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flowOn @@ -31,6 +30,7 @@ import ru.beryukhov.reactivenetwork.ReactiveNetwork class AllViewModel( dao: ItemDao, private val currentSourceRepository: CurrentSourceRepository, + favoritesRepository: FavoritesRepository, ) : ViewModel(), ToastItems by DefaultToastItems() { val observeNetwork = ReactiveNetwork() @@ -51,10 +51,8 @@ class AllViewModel( private val itemListener = fireListener() init { - combine( - itemListener.getAllShowsFlow(), - dao.getAllFavorites() - ) { f, d -> (f + d).groupBy(DbModel::url).map { it.value.fastMaxBy(DbModel::numChapters)!! } } + favoritesRepository + .getAllFavorites(itemListener) .onEach { favoriteList = it.toMutableStateList() } .launchIn(viewModelScope) diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/details/DetailsViewModel.kt b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/details/DetailsViewModel.kt index eb402c08..17c034e8 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/details/DetailsViewModel.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/details/DetailsViewModel.kt @@ -17,17 +17,16 @@ import com.kmpalette.palette.graphics.Palette import com.programmersbox.extensionloader.SourceRepository import com.programmersbox.favoritesdatabase.ChapterWatched import com.programmersbox.favoritesdatabase.DbModel -import com.programmersbox.favoritesdatabase.ItemDao import com.programmersbox.favoritesdatabase.toDbModel import com.programmersbox.gsonutils.fromJson import com.programmersbox.models.ApiService import com.programmersbox.models.ChapterModel import com.programmersbox.models.InfoModel import com.programmersbox.models.ItemModel -import com.programmersbox.sharedutils.FirebaseDb import com.programmersbox.sharedutils.TranslateItems import com.programmersbox.uiviews.GenericInfo import com.programmersbox.uiviews.presentation.Screen +import com.programmersbox.uiviews.repository.FavoritesRepository import com.programmersbox.uiviews.utils.ApiServiceDeserializer import com.programmersbox.uiviews.utils.Cached import com.programmersbox.uiviews.utils.ComposableUtils @@ -37,9 +36,9 @@ import com.programmersbox.uiviews.utils.dispatchIo import com.programmersbox.uiviews.utils.fireListener import com.programmersbox.uiviews.utils.recordFirebaseException import com.vanniktech.blurhash.BlurHash +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull @@ -53,9 +52,9 @@ import kotlinx.coroutines.launch class DetailsViewModel( handle: SavedStateHandle, genericInfo: GenericInfo, - private val dao: ItemDao, private val blurHashDao: BlurHashDao, sourceRepository: SourceRepository, + private val favoritesRepository: FavoritesRepository, ) : ViewModel() { private val details: Screen.DetailsScreen.Details? = handle.toRoute() @@ -169,10 +168,7 @@ class DetailsViewModel( suspend fun toggleNotify() { dbModel ?.let { it.copy(shouldCheckForUpdate = !it.shouldCheckForUpdate) } - ?.let { - dao.updateFavoriteItem(it) - FirebaseDb.toggleUpdateCheckShowFlow(it).collect() - } + ?.let { favoritesRepository.toggleNotify(it) } } private val englishTranslator = TranslateItems() @@ -186,25 +182,28 @@ class DetailsViewModel( } private fun setup(info: InfoModel) { - combine( - itemListener.findItemByUrlFlow(info.url), - dao.containsItem(info.url) - ) { f, d -> f || d } + favoritesRepository + .isFavorite( + url = info.url, + fireListenerClosable = itemListener, + ) .dispatchIo() .onEach { favoriteListener = it } .launchIn(viewModelScope) - combine( - chapterListener.getAllEpisodesByShowFlow(info.url), - dao.getAllChapters(info.url) - ) { f, d -> (f + d).distinctBy { it.url } } + favoritesRepository + .getChapters( + url = info.url, + fireListenerClosable = chapterListener, + ) .onEach { chapters = it } .launchIn(viewModelScope) - combine( - dao.getDbModel(info.url), - dbModelListener.getShowFlow(info.url) - ) { d, f -> f ?: d } + favoritesRepository + .getModel( + url = info.url, + fireListenerClosable = dbModelListener, + ) .onEach { dbModel = it } .launchIn(viewModelScope) } @@ -217,8 +216,11 @@ class DetailsViewModel( ) viewModelScope.launch { - if (b) dao.insertChapter(chapter) else dao.deleteChapter(chapter) - (if (b) FirebaseDb.insertEpisodeWatchedFlow(chapter) else FirebaseDb.removeEpisodeWatchedFlow(chapter)).collect() + if (b) { + favoritesRepository.addWatched(chapter) + } else { + favoritesRepository.removeWatched(chapter) + } } } @@ -227,13 +229,12 @@ class DetailsViewModel( is DetailFavoriteAction.Add -> { val db = action.info.toDbModel(action.info.chapters.size) addRemoveFavoriteJob?.cancel() - addRemoveFavoriteJob = viewModelScope.launch { - dao.insertFavorite(db) - FirebaseDb.insertShowFlow(db).collect() + addRemoveFavoriteJob = viewModelScope.launch(Dispatchers.IO) { + favoritesRepository.addFavorite(db) } imageBitmap?.let { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { blurHashDao.insertHash( BlurHashItem( action.info.imageUrl, @@ -247,12 +248,11 @@ class DetailsViewModel( is DetailFavoriteAction.Remove -> { val db = action.info.toDbModel(action.info.chapters.size) addRemoveFavoriteJob?.cancel() - addRemoveFavoriteJob = viewModelScope.launch { - dao.deleteFavorite(db) - FirebaseDb.removeShowFlow(db).collect() + addRemoveFavoriteJob = viewModelScope.launch(Dispatchers.IO) { + favoritesRepository.removeFavorite(db) } - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { blurHashItem?.let { blurHashDao.deleteHash(it) } } } diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/favorite/FavoriteViewModel.kt b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/favorite/FavoriteViewModel.kt index 5c4e74b2..c19e881b 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/favorite/FavoriteViewModel.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/favorite/FavoriteViewModel.kt @@ -6,20 +6,18 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.util.fastMap -import androidx.compose.ui.util.fastMaxBy import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.programmersbox.extensionloader.SourceRepository import com.programmersbox.favoritesdatabase.DbModel -import com.programmersbox.favoritesdatabase.ItemDao +import com.programmersbox.uiviews.repository.FavoritesRepository import com.programmersbox.uiviews.utils.fireListener -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach class FavoriteViewModel( - dao: ItemDao, private val sourceRepository: SourceRepository, + favoritesRepository: FavoritesRepository, ) : ViewModel() { private val fireListener = fireListener("favorite") @@ -31,10 +29,8 @@ class FavoriteViewModel( private val fullSourceList get() = (sourceList + favoriteList.map { it.source }).distinct() init { - combine( - fireListener.getAllShowsFlow(), - dao.getAllFavorites() - ) { f, d -> (f + d).groupBy(DbModel::url).map { it.value.fastMaxBy(DbModel::numChapters)!! } } + favoritesRepository + .getAllFavorites(fireListener) .onEach { favoriteList.clear() favoriteList.addAll(it) @@ -107,11 +103,6 @@ class FavoriteViewModel( resetSources() } } - - override fun onCleared() { - super.onCleared() - fireListener.unregister() - } } sealed class SortFavoritesBy(val sort: (Map.Entry>) -> K) { diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/recent/RecentViewModel.kt b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/recent/RecentViewModel.kt index 17a0b44f..9e6bee70 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/recent/RecentViewModel.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/recent/RecentViewModel.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.util.fastMaxBy import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.programmersbox.extensionloader.SourceRepository @@ -18,13 +17,13 @@ import com.programmersbox.models.ApiService import com.programmersbox.models.ItemModel import com.programmersbox.models.SourceInformation import com.programmersbox.uiviews.repository.CurrentSourceRepository +import com.programmersbox.uiviews.repository.FavoritesRepository import com.programmersbox.uiviews.utils.combineSources import com.programmersbox.uiviews.utils.dispatchIo import com.programmersbox.uiviews.utils.fireListener import com.programmersbox.uiviews.utils.recordFirebaseException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn @@ -39,6 +38,7 @@ class RecentViewModel( dao: ItemDao, sourceRepository: SourceRepository, currentSourceRepository: CurrentSourceRepository, + favoritesRepository: FavoritesRepository, ) : ViewModel() { var isRefreshing by mutableStateOf(false) @@ -71,10 +71,8 @@ class RecentViewModel( } .launchIn(viewModelScope) - combine( - itemListener.getAllShowsFlow(), - dao.getAllFavorites() - ) { f, d -> (f + d).groupBy(DbModel::url).map { it.value.fastMaxBy(DbModel::numChapters)!! } } + favoritesRepository + .getAllFavorites(itemListener) .onEach { favoriteList.clear() favoriteList.addAll(it) diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/settings/MoreSettingsViewModel.kt b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/settings/MoreSettingsViewModel.kt index 474b8b8c..98ec8bc8 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/presentation/settings/MoreSettingsViewModel.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/presentation/settings/MoreSettingsViewModel.kt @@ -13,6 +13,7 @@ import com.programmersbox.favoritesdatabase.ItemDao import com.programmersbox.gsonutils.fromJson import com.programmersbox.gsonutils.toJson import com.programmersbox.sharedutils.FirebaseDb +import com.programmersbox.uiviews.repository.FavoritesRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -24,6 +25,7 @@ import java.io.InputStreamReader class MoreSettingsViewModel( private val dao: ItemDao, + private val favoritesRepository: FavoritesRepository, ) : ViewModel() { var importExportListStatus: ImportExportListStatus by mutableStateOf(ImportExportListStatus.Idle) @@ -46,8 +48,7 @@ class MoreSettingsViewModel( } .onSuccess { list -> list?.forEach { - dao.insertFavorite(it) - FirebaseDb.insertShowFlow(it) + favoritesRepository.addFavorite(it) } importExportListStatus = ImportExportListStatus.Success } diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/repository/FavoritesRepository.kt b/UIViews/src/main/java/com/programmersbox/uiviews/repository/FavoritesRepository.kt new file mode 100644 index 00000000..96eb919e --- /dev/null +++ b/UIViews/src/main/java/com/programmersbox/uiviews/repository/FavoritesRepository.kt @@ -0,0 +1,70 @@ +package com.programmersbox.uiviews.repository + +import androidx.compose.ui.util.fastMaxBy +import com.programmersbox.favoritesdatabase.ChapterWatched +import com.programmersbox.favoritesdatabase.DbModel +import com.programmersbox.favoritesdatabase.ItemDao +import com.programmersbox.sharedutils.FirebaseDb +import com.programmersbox.uiviews.utils.FireListenerClosable +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine + +class FavoritesRepository( + private val dao: ItemDao, +) { + + suspend fun addFavorite(db: DbModel) { + dao.insertFavorite(db) + FirebaseDb.insertShowFlow(db).collect() + } + + suspend fun removeFavorite(db: DbModel) { + dao.deleteFavorite(db) + FirebaseDb.removeShowFlow(db).collect() + } + + suspend fun addWatched(chapterWatched: ChapterWatched) { + dao.insertChapter(chapterWatched) + FirebaseDb.insertEpisodeWatchedFlow(chapterWatched).collect() + } + + suspend fun removeWatched(chapterWatched: ChapterWatched) { + dao.deleteChapter(chapterWatched) + FirebaseDb.removeEpisodeWatchedFlow(chapterWatched).collect() + } + + suspend fun toggleNotify(db: DbModel) { + dao.updateFavoriteItem(db) + FirebaseDb.toggleUpdateCheckShowFlow(db).collect() + } + + fun isFavorite( + url: String, + fireListenerClosable: FireListenerClosable, + ) = combine( + fireListenerClosable.findItemByUrlFlow(url), + dao.containsItem(url) + ) { f, d -> f || d } + + fun getChapters( + url: String, + fireListenerClosable: FireListenerClosable, + ) = combine( + fireListenerClosable.getAllEpisodesByShowFlow(url), + dao.getAllChapters(url) + ) { f, d -> (f + d).distinctBy { it.url } } + + fun getModel( + url: String, + fireListenerClosable: FireListenerClosable, + ) = combine( + fireListenerClosable.getShowFlow(url), + dao.getDbModel(url), + ) { d, f -> d ?: f } + + fun getAllFavorites(fireListenerClosable: FireListenerClosable) = combine( + fireListenerClosable.getAllShowsFlow(), + dao.getAllFavorites() + ) { f, d -> (f + d).groupBy(DbModel::url).map { it.value.fastMaxBy(DbModel::numChapters)!! } } + +} diff --git a/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/ReadViewModel.kt b/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/ReadViewModel.kt index 0e9236cc..5a8faade 100644 --- a/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/ReadViewModel.kt +++ b/mangaworld/src/main/java/com/programmersbox/mangaworld/reader/compose/ReadViewModel.kt @@ -21,13 +21,13 @@ import com.programmersbox.mangaworld.ChapterHolder import com.programmersbox.models.ChapterModel import com.programmersbox.models.Storage import com.programmersbox.sharedutils.FirebaseDb +import com.programmersbox.uiviews.repository.FavoritesRepository import com.programmersbox.uiviews.utils.dispatchIo import com.programmersbox.uiviews.utils.fireListener import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn @@ -47,6 +47,7 @@ class ReadViewModel( savedStateHandle: SavedStateHandle, private val dao: ItemDao, private val chapterHolder: ChapterHolder, + favoritesRepository: FavoritesRepository, ) : ViewModel() { private val mangaReader: MangaReader = savedStateHandle.toRoute() @@ -134,10 +135,11 @@ class ReadViewModel( loadPages(modelPath) - combine( - itemListener.findItemByUrlFlow(mangaUrl), - dao.containsItem(mangaUrl) - ) { f, d -> f || d } + favoritesRepository + .isFavorite( + url = mangaUrl, + fireListenerClosable = itemListener + ) .dispatchIo() .onEach { addToFavorites = addToFavorites.copy(isFavorite = it) } .launchIn(viewModelScope)