Parse ingredient amounts from V1 response

This commit is contained in:
Kirill Kamakin
2022-12-04 18:47:27 +01:00
parent 8e7ccbeca1
commit a628912557
17 changed files with 472 additions and 31 deletions

View File

@@ -6,10 +6,18 @@ data class FullRecipeInfo(
val recipeYield: String,
val recipeIngredients: List<RecipeIngredientInfo>,
val recipeInstructions: List<RecipeInstructionInfo>,
val settings: RecipeSettingsInfo,
)
data class RecipeSettingsInfo(
val disableAmounts: Boolean,
)
data class RecipeIngredientInfo(
val note: String,
val quantity: Double?,
val unit: String?,
val food: String?,
)
data class RecipeInstructionInfo(

View File

@@ -8,6 +8,7 @@ import gq.kirmanak.mealient.data.baseurl.VersionInfo
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo
import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo
import gq.kirmanak.mealient.data.recipes.network.RecipeSettingsInfo
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
import gq.kirmanak.mealient.data.share.ParseRecipeURLInfo
import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity
@@ -31,6 +32,7 @@ import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeIngredientResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeInstructionResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSettingsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
@@ -40,12 +42,16 @@ import java.util.*
fun FullRecipeInfo.toRecipeEntity() = RecipeEntity(
remoteId = remoteId,
recipeYield = recipeYield
recipeYield = recipeYield,
disableAmounts = settings.disableAmounts,
)
fun RecipeIngredientInfo.toRecipeIngredientEntity(remoteId: String) = RecipeIngredientEntity(
recipeId = remoteId,
note = note,
unit = unit,
food = food,
quantity = quantity,
)
fun RecipeInstructionInfo.toRecipeInstructionEntity(remoteId: String) = RecipeInstructionEntity(
@@ -114,11 +120,15 @@ fun GetRecipeResponseV0.toFullRecipeInfo() = FullRecipeInfo(
name = name,
recipeYield = recipeYield,
recipeIngredients = recipeIngredients.map { it.toRecipeIngredientInfo() },
recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() }
recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() },
settings = RecipeSettingsInfo(disableAmounts = true)
)
fun GetRecipeIngredientResponseV0.toRecipeIngredientInfo() = RecipeIngredientInfo(
note = note,
unit = null,
food = null,
quantity = 1.0,
)
fun GetRecipeInstructionResponseV0.toRecipeInstructionInfo() = RecipeInstructionInfo(
@@ -130,11 +140,19 @@ fun GetRecipeResponseV1.toFullRecipeInfo() = FullRecipeInfo(
name = name,
recipeYield = recipeYield,
recipeIngredients = recipeIngredients.map { it.toRecipeIngredientInfo() },
recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() }
recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() },
settings = settings.toRecipeSettingsInfo(),
)
private fun GetRecipeSettingsResponseV1.toRecipeSettingsInfo() = RecipeSettingsInfo(
disableAmounts = disableAmount,
)
fun GetRecipeIngredientResponseV1.toRecipeIngredientInfo() = RecipeIngredientInfo(
note = note,
unit = unit?.name,
food = food?.name,
quantity = quantity,
)
fun GetRecipeInstructionResponseV1.toRecipeInstructionInfo() = RecipeInstructionInfo(

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.content.SharedPreferences
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.content.res.Resources
import android.os.Build
import android.view.View
import android.view.inputmethod.InputMethodManager
@@ -21,6 +22,7 @@ import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.viewbinding.ViewBinding
import com.google.android.material.textfield.TextInputLayout
import gq.kirmanak.mealient.logging.Logger
import kotlinx.coroutines.channels.ChannelResult
@@ -132,4 +134,7 @@ fun <T> LifecycleOwner.collectWhenResumed(flow: Flow<T>, collector: FlowCollecto
flow.collect(collector)
}
}
}
}
val <T : ViewBinding> T.resources: Resources
get() = root.resources

View File

@@ -4,6 +4,7 @@ import androidx.recyclerview.widget.RecyclerView
import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.databinding.ViewHolderRecipeBinding
import gq.kirmanak.mealient.extensions.resources
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.recipes.images.RecipeImageLoader
import javax.inject.Inject
@@ -30,7 +31,7 @@ class RecipeViewHolder private constructor(
}
private val loadingPlaceholder by lazy {
binding.root.resources.getString(R.string.view_holder_recipe_text_placeholder)
binding.resources.getString(R.string.view_holder_recipe_text_placeholder)
}
fun bind(item: RecipeSummaryEntity?) {

View File

@@ -19,7 +19,7 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
private val binding by viewBinding(FragmentRecipeInfoBinding::bind)
private val viewModel by viewModels<RecipeInfoViewModel>()
private val ingredientsAdapter by lazy { recipeIngredientsAdapterFactory.build() }
private lateinit var ingredientsAdapter: RecipeIngredientsAdapter
private val instructionsAdapter by lazy { recipeInstructionsAdapterFactory.build() }
@Inject
@@ -48,7 +48,6 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
logger.v { "onViewCreated() called" }
with(binding) {
ingredientsList.adapter = ingredientsAdapter
instructionsList.adapter = instructionsAdapter
}
@@ -59,6 +58,10 @@ class RecipeInfoFragment : BottomSheetDialogFragment() {
private fun onUiStateChange(uiState: RecipeInfoUiState) = with(binding) {
logger.v { "onUiStateChange() called" }
if (::ingredientsAdapter.isInitialized.not()) {
ingredientsAdapter = recipeIngredientsAdapterFactory.build(uiState.disableAmounts)
ingredientsList.adapter = ingredientsAdapter
}
ingredientsHolder.isVisible = uiState.showIngredients
instructionsGroup.isVisible = uiState.showInstructions
recipeImageLoader.loadRecipeImage(image, uiState.summaryEntity)

View File

@@ -12,4 +12,5 @@ data class RecipeInfoUiState(
val recipeInstructions: List<RecipeInstructionEntity> = emptyList(),
val title: String? = null,
val description: String? = null,
val disableAmounts: Boolean = true,
)

View File

@@ -29,6 +29,7 @@ class RecipeInfoViewModel @Inject constructor(
recipeInstructions = entity.recipeInstructions.filter { it.text.isNotBlank() },
title = entity.recipeSummaryEntity.name,
description = entity.recipeSummaryEntity.description,
disableAmounts = entity.recipeEntity.disableAmounts,
)
} ?: RecipeInfoUiState()
emit(state)

View File

@@ -2,6 +2,7 @@ package gq.kirmanak.mealient.ui.recipes.info
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
@@ -15,6 +16,7 @@ import javax.inject.Singleton
class RecipeIngredientsAdapter private constructor(
private val recipeIngredientViewHolderFactory: RecipeIngredientViewHolder.Factory,
private val logger: Logger,
private val disableAmounts: Boolean,
) : ListAdapter<RecipeIngredientEntity, RecipeIngredientViewHolder>(RecipeIngredientDiffCallback) {
@Singleton
@@ -22,12 +24,17 @@ class RecipeIngredientsAdapter private constructor(
private val recipeIngredientViewHolderFactory: RecipeIngredientViewHolder.Factory,
private val logger: Logger,
) {
fun build() = RecipeIngredientsAdapter(recipeIngredientViewHolderFactory, logger)
fun build(disableAmounts: Boolean) = RecipeIngredientsAdapter(
recipeIngredientViewHolderFactory = recipeIngredientViewHolderFactory,
logger = logger,
disableAmounts = disableAmounts,
)
}
class RecipeIngredientViewHolder private constructor(
private val binding: ViewHolderIngredientBinding,
private val logger: Logger,
private val disableAmounts: Boolean,
) : RecyclerView.ViewHolder(binding.root) {
@Singleton
@@ -35,25 +42,37 @@ class RecipeIngredientsAdapter private constructor(
private val logger: Logger,
) {
fun build(binding: ViewHolderIngredientBinding) =
RecipeIngredientViewHolder(binding, logger)
fun build(
binding: ViewHolderIngredientBinding,
disableAmounts: Boolean,
) = RecipeIngredientViewHolder(
binding = binding,
logger = logger,
disableAmounts = disableAmounts,
)
}
fun bind(item: RecipeIngredientEntity) {
logger.v { "bind() called with: item = $item" }
binding.checkBox.text = item.note
binding.checkBox.text = if (disableAmounts) {
item.note
} else {
val builder = StringBuilder()
item.quantity?.let { builder.append("${it.formatUsingMediantMethod()} ") }
item.unit?.let { builder.append("$it ") }
item.food?.let { builder.append("$it ") }
builder.toString().trim()
}
}
}
private object RecipeIngredientDiffCallback : DiffUtil.ItemCallback<RecipeIngredientEntity>() {
override fun areItemsTheSame(
oldItem: RecipeIngredientEntity,
newItem: RecipeIngredientEntity
oldItem: RecipeIngredientEntity, newItem: RecipeIngredientEntity
): Boolean = oldItem.localId == newItem.localId
override fun areContentsTheSame(
oldItem: RecipeIngredientEntity,
newItem: RecipeIngredientEntity
oldItem: RecipeIngredientEntity, newItem: RecipeIngredientEntity
): Boolean = oldItem == newItem
}
@@ -61,7 +80,8 @@ class RecipeIngredientsAdapter private constructor(
logger.v { "onCreateViewHolder() called with: parent = $parent, viewType = $viewType" }
val inflater = LayoutInflater.from(parent.context)
return recipeIngredientViewHolderFactory.build(
ViewHolderIngredientBinding.inflate(inflater, parent, false)
ViewHolderIngredientBinding.inflate(inflater, parent, false),
disableAmounts,
)
}
@@ -71,4 +91,65 @@ class RecipeIngredientsAdapter private constructor(
logger.d { "onBindViewHolder: item is $item" }
holder.bind(item)
}
}
private fun Double.formatUsingMediantMethod(d: Int = 10, mixed: Boolean = true): String {
val triple = mediantMethod(d, mixed)
return when {
triple.first == 0 -> "${triple.second}/${triple.third}"
triple.second == 0 -> "${triple.first}"
else -> "${triple.first} ${triple.second}/${triple.third}"
}
}
/**
* Rational approximation to a floating point number with bounded denominator using Mediant Method.
* For example, 333/1000 will become [0, 1, 3] (1/3), 1414/1000 will be [1, 2, 5] (1 2/5).
* Uses algorithm from this npm package - https://www.npmjs.com/package/frac
* Can be seen here https://github.com/SheetJS/frac/blob/d07f3c99c7dc059fb47d391bcb3da80f4956608e/frac.js
* @receiver - number that needs to be approximated
* @param d - maximum denominator (i.e. if 10 then 17/20 will be 4/5, if 20 then 17/20).
* @param mixed - if true returns a mixed fraction otherwise improper (i.e. "7/5" or "1 2/5")
*/
@VisibleForTesting
fun Double.mediantMethod(d: Int = 10, mixed: Boolean = true): Triple<Int, Int, Int> {
val x = this
var n1 = x.toInt()
var d1 = 1
var n2 = n1 + 1
var d2 = 1
if (x != n1.toDouble()) {
while (d1 <= d && d2 <= d) {
val m = (n1 + n2).toDouble() / (d1 + d2)
when {
x == m -> {
when {
d1 + d2 <= d -> {
d1 += d2
n1 += n2
d2 = d + 1
}
d1 > d2 -> d2 = d + 1
else -> d1 = d + 1
}
break
}
x < m -> {
n2 += n1
d2 += d1
}
else -> {
n1 += n2
d1 += d2
}
}
}
}
if (d1 > d) {
d1 = d2
n1 = n2
}
if (!mixed) return Triple(0, n1, d1)
val q = (n1.toDouble() / d1).toInt()
return Triple(q, n1 - q * d1, d1)
}

View File

@@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
import gq.kirmanak.mealient.databinding.ViewHolderInstructionBinding
import gq.kirmanak.mealient.extensions.resources
import gq.kirmanak.mealient.logging.Logger
import gq.kirmanak.mealient.ui.recipes.info.RecipeInstructionsAdapter.RecipeInstructionViewHolder
import javax.inject.Inject
@@ -52,7 +53,7 @@ class RecipeInstructionsAdapter private constructor(
fun bind(item: RecipeInstructionEntity, position: Int) {
logger.v { "bind() called with: item = $item, position = $position" }
binding.step.text = binding.root.resources.getString(
binding.step.text = binding.resources.getString(
R.string.view_holder_recipe_instructions_step, position + 1
)
binding.instruction.text = item.text

View File

@@ -8,10 +8,33 @@ import gq.kirmanak.mealient.data.baseurl.VersionInfo
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo
import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo
import gq.kirmanak.mealient.data.recipes.network.RecipeSettingsInfo
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
import gq.kirmanak.mealient.database.recipe.entity.*
import gq.kirmanak.mealient.datasource.v0.models.*
import gq.kirmanak.mealient.datasource.v1.models.*
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeIngredientV0
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeInstructionV0
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeSettingsV0
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeIngredientResponseV0
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeInstructionResponseV0
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
import gq.kirmanak.mealient.datasource.v1.models.AddRecipeIngredientV1
import gq.kirmanak.mealient.datasource.v1.models.AddRecipeInstructionV1
import gq.kirmanak.mealient.datasource.v1.models.AddRecipeSettingsV1
import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeIngredientResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeInstructionResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSettingsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
@@ -74,14 +97,23 @@ object RecipeImplTestData {
val SUGAR_INGREDIENT = RecipeIngredientInfo(
note = "2 oz of white sugar",
quantity = 1.0,
unit = null,
food = null,
)
val BREAD_INGREDIENT = RecipeIngredientInfo(
note = "2 oz of white bread",
quantity = 1.0,
unit = null,
food = null,
)
private val MILK_INGREDIENT = RecipeIngredientInfo(
note = "2 oz of white milk",
quantity = 1.0,
unit = null,
food = null,
)
val MIX_INSTRUCTION = RecipeInstructionInfo(
@@ -101,7 +133,8 @@ object RecipeImplTestData {
name = "Cake",
recipeYield = "4 servings",
recipeIngredients = listOf(SUGAR_INGREDIENT, BREAD_INGREDIENT),
recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION)
recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION),
settings = RecipeSettingsInfo(disableAmounts = true)
)
val PORRIDGE_FULL_RECIPE_INFO = FullRecipeInfo(
@@ -109,7 +142,8 @@ object RecipeImplTestData {
name = "Porridge",
recipeYield = "3 servings",
recipeIngredients = listOf(SUGAR_INGREDIENT, MILK_INGREDIENT),
recipeInstructions = listOf(MIX_INSTRUCTION, BOIL_INSTRUCTION)
recipeInstructions = listOf(MIX_INSTRUCTION, BOIL_INSTRUCTION),
settings = RecipeSettingsInfo(disableAmounts = true)
)
val MIX_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
@@ -124,17 +158,24 @@ object RecipeImplTestData {
val CAKE_RECIPE_ENTITY = RecipeEntity(
remoteId = "1",
recipeYield = "4 servings"
recipeYield = "4 servings",
disableAmounts = true,
)
val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
recipeId = "1",
note = "2 oz of white sugar",
quantity = 1.0,
unit = null,
food = null,
)
val CAKE_BREAD_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
recipeId = "1",
note = "2 oz of white bread",
quantity = 1.0,
unit = null,
food = null,
)
val FULL_CAKE_INFO_ENTITY = FullRecipeEntity(
@@ -152,17 +193,24 @@ object RecipeImplTestData {
private val PORRIDGE_RECIPE_ENTITY_FULL = RecipeEntity(
remoteId = "2",
recipeYield = "3 servings"
recipeYield = "3 servings",
disableAmounts = true,
)
private val PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
recipeId = "2",
note = "2 oz of white milk",
quantity = 1.0,
unit = null,
food = null,
)
private val PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
recipeId = "2",
note = "2 oz of white sugar",
quantity = 1.0,
unit = null,
food = null,
)
private val PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
@@ -253,11 +301,26 @@ object RecipeImplTestData {
val SUGAR_RECIPE_INGREDIENT_RESPONSE_V0 = GetRecipeIngredientResponseV0("2 oz of white sugar")
val MILK_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1("2 oz of white milk")
val MILK_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1(
note = "2 oz of white milk",
quantity = 1.0,
unit = null,
food = null,
)
val SUGAR_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1("2 oz of white sugar")
val SUGAR_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1(
note = "2 oz of white sugar",
quantity = 1.0,
unit = null,
food = null,
)
val MILK_RECIPE_INGREDIENT_INFO = RecipeIngredientInfo("2 oz of white milk")
val MILK_RECIPE_INGREDIENT_INFO = RecipeIngredientInfo(
note = "2 oz of white milk",
quantity = 1.0,
unit = null,
food = null,
)
val MIX_RECIPE_INSTRUCTION_RESPONSE_V0 = GetRecipeInstructionResponseV0("Mix the ingredients")
@@ -295,6 +358,7 @@ object RecipeImplTestData {
MIX_RECIPE_INSTRUCTION_RESPONSE_V1,
BOIL_RECIPE_INSTRUCTION_RESPONSE_V1
),
settings = GetRecipeSettingsResponseV1(disableAmount = true),
)
val MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V0 = AddRecipeInstructionV0("Mix the ingredients")

View File

@@ -0,0 +1,37 @@
package gq.kirmanak.mealient.ui.recipes.info
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
@RunWith(Parameterized::class)
class MediantMethodTest(
private val input: Triple<Double, Int, Boolean>,
private val output: Triple<Int, Int, Int>,
) {
@Test
fun `when mediantMethod is called expect correct result`() {
assertThat(input.first.mediantMethod(input.second, input.third)).isEqualTo(output)
}
companion object {
@Parameters
@JvmStatic
fun parameters(): List<Array<Any>> {
return listOf(
arrayOf(Triple(0.333, 10, true), Triple(0, 1, 3)),
arrayOf(Triple(0.333, 10, false), Triple(0, 1, 3)),
arrayOf(Triple(0.333, 100, false), Triple(0, 1, 3)),
arrayOf(Triple(0.333, 100, true), Triple(0, 1, 3)),
arrayOf(Triple(1.5, 10, true), Triple(1, 1, 2)),
arrayOf(Triple(1.5, 10, false), Triple(0, 3, 2)),
arrayOf(Triple(0.4, 10, false), Triple(0, 2, 5)),
arrayOf(Triple(0.41412412412412, 100, true), Triple(0, 41, 99)),
arrayOf(Triple(8.98, 10, true), Triple(9, 0, 1)),
)
}
}
}

View File

@@ -28,7 +28,13 @@ class RecipeInfoViewModelTest : BaseUnitTest() {
@Test
fun `when recipe is found then UI state has data`() = runTest {
val emptyNoteIngredient = RecipeIngredientEntity(recipeId = "42", note = "")
val emptyNoteIngredient = RecipeIngredientEntity(
recipeId = "42",
note = "",
food = null,
unit = null,
quantity = 1.0,
)
val returnedEntity = FULL_CAKE_INFO_ENTITY.copy(
recipeIngredients = FULL_CAKE_INFO_ENTITY.recipeIngredients + emptyNoteIngredient
)