Save isFavorite flag to DB for recipes

This commit is contained in:
Kirill Kamakin
2022-12-12 21:26:57 +01:00
parent a42758f3d1
commit aefb974cb3
23 changed files with 352 additions and 30 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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" }
}

View File

@@ -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
}
}

View File

@@ -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>
}

View File

@@ -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
}

View File

@@ -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)