Save isFavorite flag to DB for recipes
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package gq.kirmanak.mealient.data.configuration
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.MainCoroutineDispatcher
|
||||
|
||||
interface AppDispatchers {
|
||||
val io: CoroutineDispatcher
|
||||
val main: MainCoroutineDispatcher
|
||||
val default: CoroutineDispatcher
|
||||
val unconfined: CoroutineDispatcher
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package gq.kirmanak.mealient.data.configuration
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AppDispatchersImpl @Inject constructor() : AppDispatchers {
|
||||
|
||||
override val io = Dispatchers.IO
|
||||
|
||||
override val main = Dispatchers.Main
|
||||
|
||||
override val default = Dispatchers.Default
|
||||
|
||||
override val unconfined = Dispatchers.Unconfined
|
||||
}
|
||||
@@ -64,4 +64,8 @@ class MealieDataSourceWrapper @Inject constructor(
|
||||
ServerVersion.V1 -> v1Source.parseRecipeFromURL(parseRecipeURLInfo.toV1Request())
|
||||
}
|
||||
|
||||
override suspend fun getFavoriteRecipes(): List<String> = when (getVersion()) {
|
||||
ServerVersion.V0 -> v0Source.requestUserInfo().favoriteRecipes
|
||||
ServerVersion.V1 -> v1Source.requestUserInfo().favoriteRecipes
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,15 @@ package gq.kirmanak.mealient.data.recipes.db
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||
|
||||
interface RecipeStorage {
|
||||
suspend fun saveRecipes(recipes: List<RecipeSummaryInfo>)
|
||||
suspend fun saveRecipes(recipes: List<RecipeSummaryEntity>)
|
||||
|
||||
fun queryRecipes(query: String?): PagingSource<Int, RecipeSummaryEntity>
|
||||
|
||||
suspend fun refreshAll(recipes: List<RecipeSummaryInfo>)
|
||||
suspend fun refreshAll(recipes: List<RecipeSummaryEntity>)
|
||||
|
||||
suspend fun clearAllLocalData()
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package gq.kirmanak.mealient.data.recipes.db
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.withTransaction
|
||||
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||
import gq.kirmanak.mealient.database.AppDb
|
||||
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||
@@ -11,7 +10,6 @@ import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.extensions.toRecipeEntity
|
||||
import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity
|
||||
import gq.kirmanak.mealient.extensions.toRecipeInstructionEntity
|
||||
import gq.kirmanak.mealient.extensions.toRecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -23,11 +21,9 @@ class RecipeStorageImpl @Inject constructor(
|
||||
) : RecipeStorage {
|
||||
private val recipeDao: RecipeDao by lazy { db.recipeDao() }
|
||||
|
||||
override suspend fun saveRecipes(recipes: List<RecipeSummaryInfo>) {
|
||||
override suspend fun saveRecipes(recipes: List<RecipeSummaryEntity>) {
|
||||
logger.v { "saveRecipes() called with $recipes" }
|
||||
val entities = recipes.map { it.toRecipeSummaryEntity() }
|
||||
logger.v { "saveRecipes: entities = $entities" }
|
||||
db.withTransaction { recipeDao.insertRecipes(entities) }
|
||||
db.withTransaction { recipeDao.insertRecipes(recipes) }
|
||||
}
|
||||
|
||||
override fun queryRecipes(query: String?): PagingSource<Int, RecipeSummaryEntity> {
|
||||
@@ -36,7 +32,7 @@ class RecipeStorageImpl @Inject constructor(
|
||||
else recipeDao.queryRecipesByPages(query)
|
||||
}
|
||||
|
||||
override suspend fun refreshAll(recipes: List<RecipeSummaryInfo>) {
|
||||
override suspend fun refreshAll(recipes: List<RecipeSummaryEntity>) {
|
||||
logger.v { "refreshAll() called with: recipes = $recipes" }
|
||||
db.withTransaction {
|
||||
recipeDao.removeAllRecipes()
|
||||
|
||||
@@ -66,7 +66,7 @@ class RecipeRepoImpl @Inject constructor(
|
||||
override suspend fun refreshRecipes() {
|
||||
logger.v { "refreshRecipes() called" }
|
||||
runCatchingExceptCancel {
|
||||
storage.refreshAll(dataSource.requestRecipes(0, INITIAL_LOAD_PAGE_SIZE))
|
||||
mediator.updateRecipes(0, INITIAL_LOAD_PAGE_SIZE)
|
||||
}.onFailure {
|
||||
logger.e(it) { "Can't refresh recipes" }
|
||||
}
|
||||
|
||||
@@ -4,11 +4,16 @@ import androidx.annotation.VisibleForTesting
|
||||
import androidx.paging.*
|
||||
import androidx.paging.LoadType.PREPEND
|
||||
import androidx.paging.LoadType.REFRESH
|
||||
import gq.kirmanak.mealient.data.configuration.AppDispatchers
|
||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.extensions.toRecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -19,14 +24,14 @@ class RecipesRemoteMediator @Inject constructor(
|
||||
private val network: RecipeDataSource,
|
||||
private val pagingSourceFactory: RecipePagingSourceFactory,
|
||||
private val logger: Logger,
|
||||
private val dispatchers: AppDispatchers,
|
||||
) : RemoteMediator<Int, RecipeSummaryEntity>() {
|
||||
|
||||
@VisibleForTesting
|
||||
var lastRequestEnd: Int = 0
|
||||
|
||||
override suspend fun load(
|
||||
loadType: LoadType,
|
||||
state: PagingState<Int, RecipeSummaryEntity>
|
||||
loadType: LoadType, state: PagingState<Int, RecipeSummaryEntity>
|
||||
): MediatorResult {
|
||||
logger.v { "load() called with: lastRequestEnd = $lastRequestEnd, loadType = $loadType, state = $state" }
|
||||
|
||||
@@ -39,10 +44,7 @@ class RecipesRemoteMediator @Inject constructor(
|
||||
val limit = if (loadType == REFRESH) state.config.initialLoadSize else state.config.pageSize
|
||||
|
||||
val count: Int = runCatchingExceptCancel {
|
||||
val recipes = network.requestRecipes(start, limit)
|
||||
if (loadType == REFRESH) storage.refreshAll(recipes)
|
||||
else storage.saveRecipes(recipes)
|
||||
recipes.size
|
||||
updateRecipes(start, limit, loadType)
|
||||
}.getOrElse {
|
||||
logger.e(it) { "load: can't load recipes" }
|
||||
return MediatorResult.Error(it)
|
||||
@@ -58,4 +60,23 @@ class RecipesRemoteMediator @Inject constructor(
|
||||
lastRequestEnd = start + count
|
||||
return MediatorResult.Success(endOfPaginationReached = count < limit)
|
||||
}
|
||||
|
||||
suspend fun updateRecipes(
|
||||
start: Int,
|
||||
limit: Int,
|
||||
loadType: LoadType = REFRESH,
|
||||
): Int = coroutineScope {
|
||||
val deferredRecipes = async { network.requestRecipes(start, limit) }
|
||||
val favorites = network.getFavoriteRecipes().toHashSet()
|
||||
val recipes = deferredRecipes.await()
|
||||
val entities = withContext(dispatchers.default) {
|
||||
recipes.map { recipe ->
|
||||
val isFavorite = favorites.contains(recipe.slug)
|
||||
recipe.toRecipeSummaryEntity(isFavorite)
|
||||
}
|
||||
}
|
||||
if (loadType == REFRESH) storage.refreshAll(entities)
|
||||
else storage.saveRecipes(entities)
|
||||
recipes.size
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,6 @@ interface RecipeDataSource {
|
||||
suspend fun requestRecipes(start: Int, limit: Int): List<RecipeSummaryInfo>
|
||||
|
||||
suspend fun requestRecipeInfo(slug: String): FullRecipeInfo
|
||||
|
||||
suspend fun getFavoriteRecipes(): List<String>
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import gq.kirmanak.mealient.architecture.configuration.BuildConfiguration
|
||||
import gq.kirmanak.mealient.data.configuration.AppDispatchers
|
||||
import gq.kirmanak.mealient.data.configuration.AppDispatchersImpl
|
||||
import gq.kirmanak.mealient.data.configuration.BuildConfigurationImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -15,4 +17,8 @@ interface ArchitectureModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindBuildConfiguration(buildConfigurationImpl: BuildConfigurationImpl): BuildConfiguration
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindAppDispatchers(appDispatchersImpl: AppDispatchersImpl): AppDispatchers
|
||||
}
|
||||
@@ -80,7 +80,7 @@ fun GetRecipeSummaryResponseV1.toRecipeSummaryInfo() = RecipeSummaryInfo(
|
||||
imageId = remoteId,
|
||||
)
|
||||
|
||||
fun RecipeSummaryInfo.toRecipeSummaryEntity() = RecipeSummaryEntity(
|
||||
fun RecipeSummaryInfo.toRecipeSummaryEntity(isFavorite: Boolean) = RecipeSummaryEntity(
|
||||
remoteId = remoteId,
|
||||
name = name,
|
||||
slug = slug,
|
||||
@@ -88,6 +88,7 @@ fun RecipeSummaryInfo.toRecipeSummaryEntity() = RecipeSummaryEntity(
|
||||
dateAdded = dateAdded,
|
||||
dateUpdated = dateUpdated,
|
||||
imageId = imageId,
|
||||
isFavorite = isFavorite,
|
||||
)
|
||||
|
||||
fun VersionResponseV0.toVersionInfo() = VersionInfo(version)
|
||||
|
||||
Reference in New Issue
Block a user