Implement loading and saving full recipe info
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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')"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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>,
|
||||
)
|
||||
@@ -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
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
89
app/src/main/res/layout/fragment_recipe_info.xml
Normal file
89
app/src/main/res/layout/fragment_recipe_info.xml
Normal file
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/fragment_recipe_info_image_height"
|
||||
android:layout_marginBottom="@dimen/margin_small"
|
||||
android:contentDescription="@string/content_description_fragment_recipe_info_image"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:srcCompat="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:textAppearance="?textAppearanceHeadline6"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/image"
|
||||
tools:text="Best-Ever Beef Stew" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:textAppearance="?textAppearanceBody2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
tools:text="Stay warm all winter with this classic Beef Stew made with red wine and beef stock from Delish.com." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ingredients_header"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:text="@string/fragment_recipe_info_ingredients_header"
|
||||
android:textAppearance="?textAppearanceHeadline6"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/description" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/ingredients_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ingredients_header"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/view_holder_ingredient" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instructions_header"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:text="@string/fragment_recipe_info_instructions_header"
|
||||
android:textAppearance="?textAppearanceHeadline6"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ingredients_list" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/instructions_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/instructions_header"
|
||||
tools:itemCount="2"
|
||||
tools:listitem="@layout/view_holder_instruction" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
17
app/src/main/res/layout/view_holder_ingredient.xml
Normal file
17
app/src/main/res/layout/view_holder_ingredient.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="900 g braising steak/stew meat, cubed into 2.5cm pieces" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
30
app/src/main/res/layout/view_holder_instruction.xml
Normal file
30
app/src/main/res/layout/view_holder_instruction.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/step"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:textAppearance="?textAppearanceHeadline6"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Step: 1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instruction"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:textAppearance="?textAppearanceBody2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/step"
|
||||
tools:text="In a large dutch oven or heavy-bottomed pot over medium heat, heat oil. Add beef and cook until seared on all sides, 10 minutes, working in batches if necessary. Transfer beef to a plate." />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -3,4 +3,5 @@
|
||||
<dimen name="margin_small">8dp</dimen>
|
||||
<dimen name="width_view_holder_recipe_image">360dp</dimen>
|
||||
<dimen name="height_view_holder_recipe_image">180dp</dimen>
|
||||
<dimen name="fragment_recipe_info_image_height">@dimen/height_view_holder_recipe_image</dimen>
|
||||
</resources>
|
||||
@@ -8,4 +8,7 @@
|
||||
<string name="menu_main_toolbar_content_description_logout">@string/menu_main_toolbar_logout</string>
|
||||
<string name="menu_main_toolbar_logout">Logout</string>
|
||||
<string name="view_holder_recipe_text_placeholder">Loading…</string>
|
||||
<string name="content_description_fragment_recipe_info_image">@string/content_description_view_holder_recipe_image</string>
|
||||
<string name="fragment_recipe_info_ingredients_header">Ingredients</string>
|
||||
<string name="fragment_recipe_info_instructions_header">Instructions</string>
|
||||
</resources>
|
||||
@@ -1,7 +1,7 @@
|
||||
package gq.kirmanak.mealie.data.recipes
|
||||
|
||||
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
|
||||
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.GetRecipeSummaryResponse
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
@@ -68,7 +68,6 @@ object RecipeImplTestData {
|
||||
"""
|
||||
|
||||
val CAKE_RECIPE_ENTITY = RecipeSummaryEntity(
|
||||
localId = 1,
|
||||
remoteId = 1,
|
||||
name = "Cake",
|
||||
slug = "cake",
|
||||
@@ -80,7 +79,6 @@ object RecipeImplTestData {
|
||||
)
|
||||
|
||||
val PORRIDGE_RECIPE_ENTITY = RecipeSummaryEntity(
|
||||
localId = 2,
|
||||
remoteId = 2,
|
||||
name = "Porridge",
|
||||
slug = "porridge",
|
||||
|
||||
@@ -8,6 +8,10 @@ import gq.kirmanak.mealie.data.recipes.RecipeImplTestData.CAKE_RECIPE_ENTITY
|
||||
import gq.kirmanak.mealie.data.recipes.RecipeImplTestData.PORRIDGE_RECIPE_ENTITY
|
||||
import gq.kirmanak.mealie.data.recipes.RecipeImplTestData.RECIPE_SUMMARY_CAKE
|
||||
import gq.kirmanak.mealie.data.recipes.RecipeImplTestData.TEST_RECIPE_SUMMARIES
|
||||
import gq.kirmanak.mealie.data.recipes.db.entity.CategoryEntity
|
||||
import gq.kirmanak.mealie.data.recipes.db.entity.CategoryRecipeEntity
|
||||
import gq.kirmanak.mealie.data.recipes.db.entity.TagEntity
|
||||
import gq.kirmanak.mealie.data.recipes.db.entity.TagRecipeEntity
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Test
|
||||
import javax.inject.Inject
|
||||
@@ -79,9 +83,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
|
||||
val actual = mealieDb.recipeDao().queryAllRecipes()
|
||||
assertThat(actual).containsExactly(
|
||||
CAKE_RECIPE_ENTITY.copy(localId = 3),
|
||||
)
|
||||
assertThat(actual).containsExactly(CAKE_RECIPE_ENTITY)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,8 +92,8 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
|
||||
val actual = mealieDb.recipeDao().queryAllCategoryRecipes()
|
||||
assertThat(actual).containsExactly(
|
||||
CategoryRecipeEntity(categoryId = 1, recipeId = 3),
|
||||
CategoryRecipeEntity(categoryId = 2, recipeId = 3),
|
||||
CategoryRecipeEntity(categoryId = 1, recipeId = 1),
|
||||
CategoryRecipeEntity(categoryId = 2, recipeId = 1),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,8 +103,8 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
|
||||
val actual = mealieDb.recipeDao().queryAllTagRecipes()
|
||||
assertThat(actual).containsExactly(
|
||||
TagRecipeEntity(tagId = 1, recipeId = 3),
|
||||
TagRecipeEntity(tagId = 2, recipeId = 3),
|
||||
TagRecipeEntity(tagId = 1, recipeId = 1),
|
||||
TagRecipeEntity(tagId = 2, recipeId = 1),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import gq.kirmanak.mealie.data.recipes.RecipeImplTestData.PORRIDGE_RECIPE_ENTITY
|
||||
import gq.kirmanak.mealie.data.recipes.RecipeImplTestData.TEST_RECIPE_ENTITIES
|
||||
import gq.kirmanak.mealie.data.recipes.RecipeImplTestData.enqueueSuccessfulRecipeSummaryResponse
|
||||
import gq.kirmanak.mealie.data.recipes.RecipeImplTestData.enqueueUnsuccessfulRecipeSummaryResponse
|
||||
import gq.kirmanak.mealie.data.recipes.db.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealie.data.recipes.db.entity.RecipeSummaryEntity
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
Reference in New Issue
Block a user