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())
|
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 androidx.paging.PagingSource
|
||||||
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
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.FullRecipeEntity
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
|
|
||||||
interface RecipeStorage {
|
interface RecipeStorage {
|
||||||
suspend fun saveRecipes(recipes: List<RecipeSummaryInfo>)
|
suspend fun saveRecipes(recipes: List<RecipeSummaryEntity>)
|
||||||
|
|
||||||
fun queryRecipes(query: String?): PagingSource<Int, RecipeSummaryEntity>
|
fun queryRecipes(query: String?): PagingSource<Int, RecipeSummaryEntity>
|
||||||
|
|
||||||
suspend fun refreshAll(recipes: List<RecipeSummaryInfo>)
|
suspend fun refreshAll(recipes: List<RecipeSummaryEntity>)
|
||||||
|
|
||||||
suspend fun clearAllLocalData()
|
suspend fun clearAllLocalData()
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package gq.kirmanak.mealient.data.recipes.db
|
|||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
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.AppDb
|
||||||
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
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.toRecipeEntity
|
||||||
import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity
|
import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity
|
||||||
import gq.kirmanak.mealient.extensions.toRecipeInstructionEntity
|
import gq.kirmanak.mealient.extensions.toRecipeInstructionEntity
|
||||||
import gq.kirmanak.mealient.extensions.toRecipeSummaryEntity
|
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -23,11 +21,9 @@ class RecipeStorageImpl @Inject constructor(
|
|||||||
) : RecipeStorage {
|
) : RecipeStorage {
|
||||||
private val recipeDao: RecipeDao by lazy { db.recipeDao() }
|
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" }
|
logger.v { "saveRecipes() called with $recipes" }
|
||||||
val entities = recipes.map { it.toRecipeSummaryEntity() }
|
db.withTransaction { recipeDao.insertRecipes(recipes) }
|
||||||
logger.v { "saveRecipes: entities = $entities" }
|
|
||||||
db.withTransaction { recipeDao.insertRecipes(entities) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queryRecipes(query: String?): PagingSource<Int, RecipeSummaryEntity> {
|
override fun queryRecipes(query: String?): PagingSource<Int, RecipeSummaryEntity> {
|
||||||
@@ -36,7 +32,7 @@ class RecipeStorageImpl @Inject constructor(
|
|||||||
else recipeDao.queryRecipesByPages(query)
|
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" }
|
logger.v { "refreshAll() called with: recipes = $recipes" }
|
||||||
db.withTransaction {
|
db.withTransaction {
|
||||||
recipeDao.removeAllRecipes()
|
recipeDao.removeAllRecipes()
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class RecipeRepoImpl @Inject constructor(
|
|||||||
override suspend fun refreshRecipes() {
|
override suspend fun refreshRecipes() {
|
||||||
logger.v { "refreshRecipes() called" }
|
logger.v { "refreshRecipes() called" }
|
||||||
runCatchingExceptCancel {
|
runCatchingExceptCancel {
|
||||||
storage.refreshAll(dataSource.requestRecipes(0, INITIAL_LOAD_PAGE_SIZE))
|
mediator.updateRecipes(0, INITIAL_LOAD_PAGE_SIZE)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
logger.e(it) { "Can't refresh recipes" }
|
logger.e(it) { "Can't refresh recipes" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import androidx.annotation.VisibleForTesting
|
|||||||
import androidx.paging.*
|
import androidx.paging.*
|
||||||
import androidx.paging.LoadType.PREPEND
|
import androidx.paging.LoadType.PREPEND
|
||||||
import androidx.paging.LoadType.REFRESH
|
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.db.RecipeStorage
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
|
import gq.kirmanak.mealient.extensions.toRecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
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.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -19,14 +24,14 @@ class RecipesRemoteMediator @Inject constructor(
|
|||||||
private val network: RecipeDataSource,
|
private val network: RecipeDataSource,
|
||||||
private val pagingSourceFactory: RecipePagingSourceFactory,
|
private val pagingSourceFactory: RecipePagingSourceFactory,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
|
private val dispatchers: AppDispatchers,
|
||||||
) : RemoteMediator<Int, RecipeSummaryEntity>() {
|
) : RemoteMediator<Int, RecipeSummaryEntity>() {
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
var lastRequestEnd: Int = 0
|
var lastRequestEnd: Int = 0
|
||||||
|
|
||||||
override suspend fun load(
|
override suspend fun load(
|
||||||
loadType: LoadType,
|
loadType: LoadType, state: PagingState<Int, RecipeSummaryEntity>
|
||||||
state: PagingState<Int, RecipeSummaryEntity>
|
|
||||||
): MediatorResult {
|
): MediatorResult {
|
||||||
logger.v { "load() called with: lastRequestEnd = $lastRequestEnd, loadType = $loadType, state = $state" }
|
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 limit = if (loadType == REFRESH) state.config.initialLoadSize else state.config.pageSize
|
||||||
|
|
||||||
val count: Int = runCatchingExceptCancel {
|
val count: Int = runCatchingExceptCancel {
|
||||||
val recipes = network.requestRecipes(start, limit)
|
updateRecipes(start, limit, loadType)
|
||||||
if (loadType == REFRESH) storage.refreshAll(recipes)
|
|
||||||
else storage.saveRecipes(recipes)
|
|
||||||
recipes.size
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
logger.e(it) { "load: can't load recipes" }
|
logger.e(it) { "load: can't load recipes" }
|
||||||
return MediatorResult.Error(it)
|
return MediatorResult.Error(it)
|
||||||
@@ -58,4 +60,23 @@ class RecipesRemoteMediator @Inject constructor(
|
|||||||
lastRequestEnd = start + count
|
lastRequestEnd = start + count
|
||||||
return MediatorResult.Success(endOfPaginationReached = count < limit)
|
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 requestRecipes(start: Int, limit: Int): List<RecipeSummaryInfo>
|
||||||
|
|
||||||
suspend fun requestRecipeInfo(slug: String): FullRecipeInfo
|
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.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import gq.kirmanak.mealient.architecture.configuration.BuildConfiguration
|
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 gq.kirmanak.mealient.data.configuration.BuildConfigurationImpl
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -15,4 +17,8 @@ interface ArchitectureModule {
|
|||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
fun bindBuildConfiguration(buildConfigurationImpl: BuildConfigurationImpl): BuildConfiguration
|
fun bindBuildConfiguration(buildConfigurationImpl: BuildConfigurationImpl): BuildConfiguration
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindAppDispatchers(appDispatchersImpl: AppDispatchersImpl): AppDispatchers
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ fun GetRecipeSummaryResponseV1.toRecipeSummaryInfo() = RecipeSummaryInfo(
|
|||||||
imageId = remoteId,
|
imageId = remoteId,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun RecipeSummaryInfo.toRecipeSummaryEntity() = RecipeSummaryEntity(
|
fun RecipeSummaryInfo.toRecipeSummaryEntity(isFavorite: Boolean) = RecipeSummaryEntity(
|
||||||
remoteId = remoteId,
|
remoteId = remoteId,
|
||||||
name = name,
|
name = name,
|
||||||
slug = slug,
|
slug = slug,
|
||||||
@@ -88,6 +88,7 @@ fun RecipeSummaryInfo.toRecipeSummaryEntity() = RecipeSummaryEntity(
|
|||||||
dateAdded = dateAdded,
|
dateAdded = dateAdded,
|
||||||
dateUpdated = dateUpdated,
|
dateUpdated = dateUpdated,
|
||||||
imageId = imageId,
|
imageId = imageId,
|
||||||
|
isFavorite = isFavorite,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun VersionResponseV0.toVersionInfo() = VersionInfo(version)
|
fun VersionResponseV0.toVersionInfo() = VersionInfo(version)
|
||||||
|
|||||||
198
database/schemas/gq.kirmanak.mealient.database.AppDb/8.json
Normal file
198
database/schemas/gq.kirmanak.mealient.database.AppDb/8.json
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 8,
|
||||||
|
"identityHash": "793673e401425db36544918dae6bf4c1",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "recipe_summaries",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `description` TEXT NOT NULL, `date_added` INTEGER NOT NULL, `date_updated` INTEGER NOT NULL, `image_id` TEXT, `is_favorite` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "slug",
|
||||||
|
"columnName": "slug",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateAdded",
|
||||||
|
"columnName": "date_added",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateUpdated",
|
||||||
|
"columnName": "date_updated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageId",
|
||||||
|
"columnName": "image_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isFavorite",
|
||||||
|
"columnName": "is_favorite",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `recipe_yield` TEXT NOT NULL, `disable_amounts` INTEGER NOT NULL DEFAULT true, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeYield",
|
||||||
|
"columnName": "recipe_yield",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "disableAmounts",
|
||||||
|
"columnName": "disable_amounts",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_ingredient",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `note` TEXT NOT NULL, `food` TEXT, `unit` TEXT, `quantity` REAL, `title` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "note",
|
||||||
|
"columnName": "note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "food",
|
||||||
|
"columnName": "food",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "unit",
|
||||||
|
"columnName": "unit",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "quantity",
|
||||||
|
"columnName": "quantity",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_instruction",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "text",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '793673e401425db36544918dae6bf4c1')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import gq.kirmanak.mealient.database.recipe.RecipeDao
|
|||||||
import gq.kirmanak.mealient.database.recipe.entity.*
|
import gq.kirmanak.mealient.database.recipe.entity.*
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
version = 7,
|
version = 8,
|
||||||
entities = [
|
entities = [
|
||||||
RecipeSummaryEntity::class,
|
RecipeSummaryEntity::class,
|
||||||
RecipeEntity::class,
|
RecipeEntity::class,
|
||||||
@@ -20,6 +20,7 @@ import gq.kirmanak.mealient.database.recipe.entity.*
|
|||||||
AutoMigration(from = 4, to = 5, spec = AppDb.From4To5Migration::class),
|
AutoMigration(from = 4, to = 5, spec = AppDb.From4To5Migration::class),
|
||||||
AutoMigration(from = 5, to = 6, spec = AppDb.From5To6Migration::class),
|
AutoMigration(from = 5, to = 6, spec = AppDb.From5To6Migration::class),
|
||||||
AutoMigration(from = 6, to = 7),
|
AutoMigration(from = 6, to = 7),
|
||||||
|
AutoMigration(from = 7, to = 8),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@TypeConverters(RoomTypeConverters::class)
|
@TypeConverters(RoomTypeConverters::class)
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ data class RecipeSummaryEntity(
|
|||||||
@ColumnInfo(name = "date_added") val dateAdded: LocalDate,
|
@ColumnInfo(name = "date_added") val dateAdded: LocalDate,
|
||||||
@ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime,
|
@ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime,
|
||||||
@ColumnInfo(name = "image_id") val imageId: String?,
|
@ColumnInfo(name = "image_id") val imageId: String?,
|
||||||
|
@ColumnInfo(name = "is_favorite", defaultValue = "false") val isFavorite: Boolean,
|
||||||
)
|
)
|
||||||
@@ -5,13 +5,13 @@ interface NetworkRequestWrapper {
|
|||||||
suspend fun <T> makeCall(
|
suspend fun <T> makeCall(
|
||||||
block: suspend () -> T,
|
block: suspend () -> T,
|
||||||
logMethod: () -> String,
|
logMethod: () -> String,
|
||||||
logParameters: () -> String,
|
logParameters: (() -> String)? = null,
|
||||||
): Result<T>
|
): Result<T>
|
||||||
|
|
||||||
suspend fun <T> makeCallAndHandleUnauthorized(
|
suspend fun <T> makeCallAndHandleUnauthorized(
|
||||||
block: suspend () -> T,
|
block: suspend () -> T,
|
||||||
logMethod: () -> String,
|
logMethod: () -> String,
|
||||||
logParameters: () -> String,
|
logParameters: (() -> String)? = null,
|
||||||
): T
|
): T
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -16,18 +16,40 @@ class NetworkRequestWrapperImpl @Inject constructor(
|
|||||||
override suspend fun <T> makeCall(
|
override suspend fun <T> makeCall(
|
||||||
block: suspend () -> T,
|
block: suspend () -> T,
|
||||||
logMethod: () -> String,
|
logMethod: () -> String,
|
||||||
logParameters: () -> String,
|
logParameters: (() -> String)?,
|
||||||
): Result<T> {
|
): Result<T> {
|
||||||
logger.v { "${logMethod()} called with: ${logParameters()}" }
|
logger.v {
|
||||||
|
if (logParameters == null) {
|
||||||
|
"${logMethod()} called"
|
||||||
|
} else {
|
||||||
|
"${logMethod()} called with: ${logParameters()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
return runCatchingExceptCancel { block() }
|
return runCatchingExceptCancel { block() }
|
||||||
.onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } }
|
.onFailure {
|
||||||
.onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}, result = $it" } }
|
logger.e(it) {
|
||||||
|
if (logParameters == null) {
|
||||||
|
"${logMethod()} request failed"
|
||||||
|
} else {
|
||||||
|
"${logMethod()} request failed with: ${logParameters()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onSuccess {
|
||||||
|
logger.d {
|
||||||
|
if (logParameters == null) {
|
||||||
|
"${logMethod()} request succeeded, result = $it"
|
||||||
|
} else {
|
||||||
|
"${logMethod()} request succeeded with: ${logParameters()}, result = $it"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun <T> makeCallAndHandleUnauthorized(
|
override suspend fun <T> makeCallAndHandleUnauthorized(
|
||||||
block: suspend () -> T,
|
block: suspend () -> T,
|
||||||
logMethod: () -> String,
|
logMethod: () -> String,
|
||||||
logParameters: () -> String
|
logParameters: (() -> String)?
|
||||||
): T = makeCall(block, logMethod, logParameters).getOrElse {
|
): T = makeCall(block, logMethod, logParameters).getOrElse {
|
||||||
throw if (it is HttpException && it.code() in listOf(401, 403)) {
|
throw if (it is HttpException && it.code() in listOf(401, 403)) {
|
||||||
NetworkError.Unauthorized(it)
|
NetworkError.Unauthorized(it)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
|
|||||||
import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenRequestV0
|
import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenRequestV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetUserInfoResponseV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0
|
import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
||||||
|
|
||||||
@@ -21,8 +22,7 @@ interface MealieDataSourceV0 {
|
|||||||
password: String,
|
password: String,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
suspend fun getVersionInfo(
|
suspend fun getVersionInfo(): VersionResponseV0
|
||||||
): VersionResponseV0
|
|
||||||
|
|
||||||
suspend fun requestRecipes(
|
suspend fun requestRecipes(
|
||||||
start: Int,
|
start: Int,
|
||||||
@@ -40,4 +40,6 @@ interface MealieDataSourceV0 {
|
|||||||
suspend fun createApiToken(
|
suspend fun createApiToken(
|
||||||
request: CreateApiTokenRequestV0,
|
request: CreateApiTokenRequestV0,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
|
suspend fun requestUserInfo(): GetUserInfoResponseV0
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenRequestV0
|
|||||||
import gq.kirmanak.mealient.datasource.v0.models.ErrorDetailV0
|
import gq.kirmanak.mealient.datasource.v0.models.ErrorDetailV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetUserInfoResponseV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0
|
import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0
|
||||||
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
@@ -49,7 +50,6 @@ class MealieDataSourceV0Impl @Inject constructor(
|
|||||||
override suspend fun getVersionInfo(): VersionResponseV0 = networkRequestWrapper.makeCall(
|
override suspend fun getVersionInfo(): VersionResponseV0 = networkRequestWrapper.makeCall(
|
||||||
block = { service.getVersion() },
|
block = { service.getVersion() },
|
||||||
logMethod = { "getVersionInfo" },
|
logMethod = { "getVersionInfo" },
|
||||||
logParameters = { "" },
|
|
||||||
).getOrElse {
|
).getOrElse {
|
||||||
throw when (it) {
|
throw when (it) {
|
||||||
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
||||||
@@ -90,4 +90,11 @@ class MealieDataSourceV0Impl @Inject constructor(
|
|||||||
logMethod = { "createApiToken" },
|
logMethod = { "createApiToken" },
|
||||||
logParameters = { "request = $request" }
|
logParameters = { "request = $request" }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun requestUserInfo(): GetUserInfoResponseV0 {
|
||||||
|
return networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.getUserSelfInfo() },
|
||||||
|
logMethod = { "requestUserInfo" },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,4 +40,7 @@ interface MealieServiceV0 {
|
|||||||
suspend fun createApiToken(
|
suspend fun createApiToken(
|
||||||
@Body request: CreateApiTokenRequestV0,
|
@Body request: CreateApiTokenRequestV0,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
|
@GET("/api/users/self")
|
||||||
|
suspend fun getUserSelfInfo(): GetUserInfoResponseV0
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v0.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetUserInfoResponseV0(
|
||||||
|
@SerialName("favoriteRecipes") val favoriteRecipes: List<String> = emptyList(),
|
||||||
|
)
|
||||||
@@ -5,6 +5,7 @@ import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenResponseV1
|
|||||||
import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
|
import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetUserInfoResponseV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
|
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
|
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
|
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
|
||||||
@@ -47,4 +48,6 @@ interface MealieDataSourceV1 {
|
|||||||
suspend fun createApiToken(
|
suspend fun createApiToken(
|
||||||
request: CreateApiTokenRequestV1,
|
request: CreateApiTokenRequestV1,
|
||||||
): CreateApiTokenResponseV1
|
): CreateApiTokenResponseV1
|
||||||
|
|
||||||
|
suspend fun requestUserInfo(): GetUserInfoResponseV1
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
|
|||||||
import gq.kirmanak.mealient.datasource.v1.models.ErrorDetailV1
|
import gq.kirmanak.mealient.datasource.v1.models.ErrorDetailV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
|
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.GetUserInfoResponseV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
|
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
|
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
|
||||||
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
|
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
|
||||||
@@ -60,7 +61,6 @@ class MealieDataSourceV1Impl @Inject constructor(
|
|||||||
override suspend fun getVersionInfo(): VersionResponseV1 = networkRequestWrapper.makeCall(
|
override suspend fun getVersionInfo(): VersionResponseV1 = networkRequestWrapper.makeCall(
|
||||||
block = { service.getVersion() },
|
block = { service.getVersion() },
|
||||||
logMethod = { "getVersionInfo" },
|
logMethod = { "getVersionInfo" },
|
||||||
logParameters = { "" },
|
|
||||||
).getOrElse {
|
).getOrElse {
|
||||||
throw when (it) {
|
throw when (it) {
|
||||||
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
||||||
@@ -101,5 +101,12 @@ class MealieDataSourceV1Impl @Inject constructor(
|
|||||||
logMethod = { "createApiToken" },
|
logMethod = { "createApiToken" },
|
||||||
logParameters = { "request = $request" }
|
logParameters = { "request = $request" }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun requestUserInfo(): GetUserInfoResponseV1 {
|
||||||
|
return networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.getUserSelfInfo() },
|
||||||
|
logMethod = { "requestUserInfo" },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,4 +46,7 @@ interface MealieServiceV1 {
|
|||||||
suspend fun createApiToken(
|
suspend fun createApiToken(
|
||||||
@Body request: CreateApiTokenRequestV1,
|
@Body request: CreateApiTokenRequestV1,
|
||||||
): CreateApiTokenResponseV1
|
): CreateApiTokenResponseV1
|
||||||
|
|
||||||
|
@GET("/api/users/self")
|
||||||
|
suspend fun getUserSelfInfo(): GetUserInfoResponseV1
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v1.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetUserInfoResponseV1(
|
||||||
|
@SerialName("favoriteRecipes") val favoriteRecipes: List<String> = emptyList(),
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user