Implement loading and saving full recipe info

This commit is contained in:
Kirill Kamakin
2021-11-17 20:44:14 +03:00
parent de6ca65b19
commit 7ebe89adfc
35 changed files with 355 additions and 39 deletions

View File

@@ -4,7 +4,8 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import gq.kirmanak.mealie.data.impl.RoomTypeConverters
import gq.kirmanak.mealie.data.recipes.db.*
import gq.kirmanak.mealie.data.recipes.db.RecipeDao
import gq.kirmanak.mealie.data.recipes.db.entity.*
import javax.inject.Singleton
@Database(

View File

@@ -1,7 +1,7 @@
package gq.kirmanak.mealie.data.recipes
import androidx.paging.Pager
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
interface RecipeRepo {
fun createPager(): Pager<Int, RecipeSummaryEntity>

View File

@@ -5,6 +5,7 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import gq.kirmanak.mealie.data.recipes.db.entity.*
@Dao
interface RecipeDao {
@@ -18,7 +19,7 @@ interface RecipeDao {
fun queryRecipesByPages(): PagingSource<Int, RecipeSummaryEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertRecipe(recipeSummaryEntity: RecipeSummaryEntity): Long
suspend fun insertRecipe(recipeSummaryEntity: RecipeSummaryEntity)
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertTag(tagEntity: TagEntity): Long
@@ -55,4 +56,13 @@ interface RecipeDao {
@Query("SELECT * FROM tag_recipe")
suspend fun queryAllTagRecipes(): List<TagRecipeEntity>
@Insert
suspend fun insertRecipe(recipe: RecipeEntity)
@Insert
suspend fun insertRecipeInstructions(instructions: List<RecipeInstructionEntity>)
@Insert
suspend fun insertRecipeIngredients(ingredients: List<RecipeIngredientEntity>)
}

View File

@@ -1,7 +1,9 @@
package gq.kirmanak.mealie.data.recipes.db
import androidx.paging.PagingSource
import gq.kirmanak.mealie.data.recipes.network.GetRecipeSummaryResponse
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeSummaryResponse
interface RecipeStorage {
suspend fun saveRecipes(recipes: List<GetRecipeSummaryResponse>)
@@ -11,4 +13,6 @@ interface RecipeStorage {
suspend fun refreshAll(recipes: List<GetRecipeSummaryResponse>)
suspend fun clearAllLocalData()
suspend fun saveRecipeInfo(recipe: GetRecipeResponse)
}

View File

@@ -3,7 +3,11 @@ package gq.kirmanak.mealie.data.recipes.db
import androidx.paging.PagingSource
import androidx.room.withTransaction
import gq.kirmanak.mealie.data.MealieDb
import gq.kirmanak.mealie.data.recipes.network.GetRecipeSummaryResponse
import gq.kirmanak.mealie.data.recipes.db.entity.*
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeIngredientResponse
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeInstructionResponse
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeSummaryResponse
import timber.log.Timber
import javax.inject.Inject
@@ -27,16 +31,20 @@ class RecipeStorageImpl @Inject constructor(
val categoryRecipeEntities = mutableSetOf<CategoryRecipeEntity>()
for (recipe in recipes) {
val recipeId = recipeDao.insertRecipe(recipe.recipeEntity())
val recipeSummaryEntity = recipe.recipeEntity()
recipeDao.insertRecipe(recipeSummaryEntity)
for (tag in recipe.tags) {
val tagId = getIdOrInsert(tagEntities, tag)
tagRecipeEntities += TagRecipeEntity(tagId, recipeId)
tagRecipeEntities += TagRecipeEntity(tagId, recipeSummaryEntity.remoteId)
}
for (category in recipe.recipeCategories) {
val categoryId = getOrInsert(categoryEntities, category)
categoryRecipeEntities += CategoryRecipeEntity(categoryId, recipeId)
categoryRecipeEntities += CategoryRecipeEntity(
categoryId,
recipeSummaryEntity.remoteId
)
}
}
@@ -108,4 +116,42 @@ class RecipeStorageImpl @Inject constructor(
recipeDao.removeAllTags()
}
}
override suspend fun saveRecipeInfo(recipe: GetRecipeResponse) {
Timber.v("saveRecipeInfo() called with: recipe = $recipe")
db.withTransaction {
recipeDao.insertRecipe(recipe.toRecipeEntity())
val ingredients = recipe.recipeIngredients.map {
it.toRecipeIngredientEntity(recipe.remoteId)
}
recipeDao.insertRecipeIngredients(ingredients)
val instructions = recipe.recipeInstructions.map {
it.toRecipeInstructionEntity(recipe.remoteId)
}
recipeDao.insertRecipeInstructions(instructions)
}
}
private fun GetRecipeResponse.toRecipeEntity() = RecipeEntity(
remoteId = remoteId,
recipeYield = recipeYield
)
private fun GetRecipeIngredientResponse.toRecipeIngredientEntity(remoteId: Long) =
RecipeIngredientEntity(
recipeId = remoteId,
title = title,
note = note,
unit = unit,
food = food,
disableAmount = disableAmount,
quantity = quantity
)
private fun GetRecipeInstructionResponse.toRecipeInstructionEntity(remoteId: Long) =
RecipeInstructionEntity(
recipeId = remoteId,
title = title,
text = text
)
}

View File

@@ -1,4 +1,4 @@
package gq.kirmanak.mealie.data.recipes.db
package gq.kirmanak.mealie.data.recipes.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity

View File

@@ -1,4 +1,4 @@
package gq.kirmanak.mealie.data.recipes.db
package gq.kirmanak.mealie.data.recipes.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
@@ -17,7 +17,7 @@ import androidx.room.Index
onUpdate = ForeignKey.CASCADE
), ForeignKey(
entity = RecipeSummaryEntity::class,
parentColumns = ["local_id"],
parentColumns = ["remote_id"],
childColumns = ["recipe_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE

View File

@@ -0,0 +1,11 @@
package gq.kirmanak.mealie.data.recipes.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "recipe")
data class RecipeEntity(
@PrimaryKey @ColumnInfo(name = "remote_id") val remoteId: Long,
@ColumnInfo(name = "recipe_yield") val recipeYield: String,
)

View File

@@ -0,0 +1,16 @@
package gq.kirmanak.mealie.data.recipes.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "recipe_ingredient")
data class RecipeIngredientEntity(
@PrimaryKey @ColumnInfo(name = "recipe_id") val recipeId: Long,
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "note") val note: String,
@ColumnInfo(name = "unit") val unit: String,
@ColumnInfo(name = "food") val food: String,
@ColumnInfo(name = "disable_amount") val disableAmount: Boolean,
@ColumnInfo(name = "quantity") val quantity: Int,
)

View File

@@ -0,0 +1,12 @@
package gq.kirmanak.mealie.data.recipes.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "recipe_instruction")
data class RecipeInstructionEntity(
@PrimaryKey @ColumnInfo(name = "recipe_id") val recipeId: Long,
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "text") val text: String,
)

View File

@@ -1,16 +1,14 @@
package gq.kirmanak.mealie.data.recipes.db
package gq.kirmanak.mealie.data.recipes.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
@Entity(tableName = "recipe_summaries", indices = [Index(value = ["remote_id"], unique = true)])
@Entity(tableName = "recipe_summaries")
data class RecipeSummaryEntity(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
@ColumnInfo(name = "remote_id") val remoteId: Long,
@PrimaryKey @ColumnInfo(name = "remote_id") val remoteId: Long,
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "slug") val slug: String,
@ColumnInfo(name = "image") val image: String,
@@ -20,6 +18,6 @@ data class RecipeSummaryEntity(
@ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime
) {
override fun toString(): String {
return "RecipeEntity(localId=$localId, remoteId=$remoteId, name='$name')"
return "RecipeEntity(remoteId=$remoteId, name='$name')"
}
}

View File

@@ -1,4 +1,4 @@
package gq.kirmanak.mealie.data.recipes.db
package gq.kirmanak.mealie.data.recipes.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity

View File

@@ -1,4 +1,4 @@
package gq.kirmanak.mealie.data.recipes.db
package gq.kirmanak.mealie.data.recipes.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
@@ -15,7 +15,7 @@ import androidx.room.ForeignKey
onUpdate = ForeignKey.CASCADE
), ForeignKey(
entity = RecipeSummaryEntity::class,
parentColumns = ["local_id"],
parentColumns = ["remote_id"],
childColumns = ["recipe_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE

View File

@@ -2,7 +2,7 @@ package gq.kirmanak.mealie.data.recipes.impl
import androidx.paging.PagingSource
import gq.kirmanak.mealie.data.recipes.db.RecipeStorage
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

View File

@@ -5,7 +5,7 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig
import gq.kirmanak.mealie.data.recipes.RecipeRepo
import gq.kirmanak.mealie.data.recipes.db.RecipeStorage
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
import timber.log.Timber
import javax.inject.Inject

View File

@@ -8,7 +8,7 @@ import androidx.paging.LoadType.REFRESH
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import gq.kirmanak.mealie.data.recipes.db.RecipeStorage
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.network.RecipeDataSource
import kotlinx.coroutines.CancellationException
import timber.log.Timber

View File

@@ -1,5 +1,10 @@
package gq.kirmanak.mealie.data.recipes.network
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeSummaryResponse
interface RecipeDataSource {
suspend fun requestRecipes(start: Int = 0, limit: Int = 9999): List<GetRecipeSummaryResponse>
suspend fun requestRecipeInfo(slug: String): GetRecipeResponse
}

View File

@@ -2,6 +2,8 @@ package gq.kirmanak.mealie.data.recipes.network
import gq.kirmanak.mealie.data.auth.AuthRepo
import gq.kirmanak.mealie.data.impl.RetrofitBuilder
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeSummaryResponse
import kotlinx.serialization.ExperimentalSerializationApi
import timber.log.Timber
import javax.inject.Inject
@@ -21,6 +23,14 @@ class RecipeDataSourceImpl @Inject constructor(
return recipeSummary
}
override suspend fun requestRecipeInfo(slug: String): GetRecipeResponse {
Timber.v("requestRecipeInfo() called with: slug = $slug")
val service: RecipeService = getRecipeService()
val recipeInfo = service.getRecipe(slug)
Timber.v("requestRecipeInfo() returned: $recipeInfo")
return recipeInfo
}
private suspend fun getRecipeService(): RecipeService {
Timber.v("getRecipeService() called")
val cachedService: RecipeService? = _recipeService

View File

@@ -1,6 +1,9 @@
package gq.kirmanak.mealie.data.recipes.network
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeResponse
import gq.kirmanak.mealie.data.recipes.network.response.GetRecipeSummaryResponse
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
interface RecipeService {
@@ -9,4 +12,9 @@ interface RecipeService {
@Query("start") start: Int,
@Query("limit") limit: Int
): List<GetRecipeSummaryResponse>
@GET("/api/recipes/:recipe_slug")
suspend fun getRecipe(
@Path("recipe_slug") recipeSlug: String
): GetRecipeResponse
}

View File

@@ -0,0 +1,14 @@
package gq.kirmanak.mealie.data.recipes.network.response
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GetRecipeIngredientResponse(
@SerialName("title") val title: String = "",
@SerialName("note") val note: String = "",
@SerialName("unit") val unit: String = "",
@SerialName("food") val food: String = "",
@SerialName("disableAmount") val disableAmount: Boolean,
@SerialName("quantity") val quantity: Int,
)

View File

@@ -0,0 +1,10 @@
package gq.kirmanak.mealie.data.recipes.network.response
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GetRecipeInstructionResponse(
@SerialName("title") val title: String = "",
@SerialName("text") val text: String,
)

View File

@@ -0,0 +1,23 @@
package gq.kirmanak.mealie.data.recipes.network.response
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GetRecipeResponse(
@SerialName("id") val remoteId: Long,
@SerialName("name") val name: String,
@SerialName("slug") val slug: String,
@SerialName("image") val image: String,
@SerialName("description") val description: String = "",
@SerialName("recipeCategory") val recipeCategories: List<String>,
@SerialName("tags") val tags: List<String>,
@SerialName("rating") val rating: Int?,
@SerialName("dateAdded") val dateAdded: LocalDate,
@SerialName("dateUpdated") val dateUpdated: LocalDateTime,
@SerialName("recipeYield") val recipeYield: String = "",
@SerialName("recipeIngredient") val recipeIngredients: List<GetRecipeIngredientResponse>,
@SerialName("recipeInstructions") val recipeInstructions: List<GetRecipeInstructionResponse>,
)

View File

@@ -1,4 +1,4 @@
package gq.kirmanak.mealie.data.recipes.network
package gq.kirmanak.mealie.data.recipes.network.response
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime

View File

@@ -2,7 +2,7 @@ package gq.kirmanak.mealie.ui.recipes
import androidx.recyclerview.widget.RecyclerView
import gq.kirmanak.mealie.R
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
import gq.kirmanak.mealie.databinding.ViewHolderRecipeBinding
class RecipeViewHolder(

View File

@@ -6,7 +6,7 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import gq.kirmanak.mealie.data.recipes.RecipeImageLoader
import gq.kirmanak.mealie.data.recipes.RecipeRepo
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
import kotlinx.coroutines.launch
import javax.inject.Inject

View File

@@ -4,7 +4,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
import gq.kirmanak.mealie.databinding.ViewHolderRecipeBinding
import timber.log.Timber

View File

@@ -0,0 +1,8 @@
package gq.kirmanak.mealie.ui.recipes.info
import androidx.fragment.app.Fragment
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class RecipeInfoFragment : Fragment() {
}