Add linked ingredients to recipe step (#177)

* Add Compose to app module

* Move Theme to ui module

* Add Coil image loader

* Use Compose for recipe screen

* Save instruction to ingredient relation to DB

* Display ingredients as server formats them

* Display linked ingredients under each step

* Fix ingredients padding

* Show recipe full screen

* Fix recipe screen UI issues

* Hide keyboard on recipe navigation

* Fix loading recipes from DB with no instructions or ingredients

* Add instructions section title

* Add ingredients section title

* Remove unused view holders
This commit is contained in:
Kirill Kamakin
2023-11-07 20:47:01 +01:00
committed by GitHub
parent 5ed1acb678
commit 941d45328e
46 changed files with 797 additions and 730 deletions

View File

@@ -10,7 +10,9 @@ import gq.kirmanak.mealient.database.CAKE_RECIPE_ENTITY
import gq.kirmanak.mealient.database.CAKE_RECIPE_SUMMARY_ENTITY
import gq.kirmanak.mealient.database.CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY
import gq.kirmanak.mealient.database.FULL_CAKE_INFO_ENTITY
import gq.kirmanak.mealient.database.MIX_BREAD_RECIPE_INGREDIENT_INSTRUCTION_ENTITY
import gq.kirmanak.mealient.database.MIX_CAKE_RECIPE_INSTRUCTION_ENTITY
import gq.kirmanak.mealient.database.MIX_SUGAR_RECIPE_INGREDIENT_INSTRUCTION_ENTITY
import gq.kirmanak.mealient.database.recipe.RecipeStorage
import gq.kirmanak.mealient.datasource.NetworkError.Unauthorized
import gq.kirmanak.mealient.datasource_test.CAKE_RECIPE_RESPONSE
@@ -78,7 +80,13 @@ class RecipeRepoTest : BaseUnitTest() {
CAKE_BREAD_RECIPE_INGREDIENT_ENTITY
)
),
eq(listOf(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY, BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY))
eq(listOf(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY, BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY)),
eq(
listOf(
MIX_SUGAR_RECIPE_INGREDIENT_INSTRUCTION_ENTITY,
MIX_BREAD_RECIPE_INGREDIENT_INSTRUCTION_ENTITY
)
),
)
}
}

View File

@@ -1,37 +0,0 @@
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

@@ -1,9 +1,13 @@
package gq.kirmanak.mealient.ui.recipes.info
import androidx.lifecycle.asFlow
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.data.recipes.RecipeRepo
import gq.kirmanak.mealient.data.recipes.impl.RecipeImageUrlProvider
import gq.kirmanak.mealient.database.BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY
import gq.kirmanak.mealient.database.CAKE_BREAD_RECIPE_INGREDIENT_ENTITY
import gq.kirmanak.mealient.database.CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY
import gq.kirmanak.mealient.database.FULL_CAKE_INFO_ENTITY
import gq.kirmanak.mealient.database.MIX_CAKE_RECIPE_INSTRUCTION_ENTITY
import gq.kirmanak.mealient.test.BaseUnitTest
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
@@ -16,10 +20,13 @@ class RecipeInfoViewModelTest : BaseUnitTest() {
@MockK
lateinit var recipeRepo: RecipeRepo
@MockK
lateinit var recipeImageUrlProvider: RecipeImageUrlProvider
@Test
fun `when recipe isn't found then UI state is empty`() = runTest {
coEvery { recipeRepo.loadRecipeInfo(eq(RECIPE_ID)) } returns null
val uiState = createSubject().uiState.asFlow().first()
val uiState = createSubject().uiState.first()
assertThat(uiState).isEqualTo(RecipeInfoUiState())
}
@@ -29,22 +36,30 @@ class RecipeInfoViewModelTest : BaseUnitTest() {
recipeIngredients = FULL_CAKE_INFO_ENTITY.recipeIngredients
)
coEvery { recipeRepo.loadRecipeInfo(eq(RECIPE_ID)) } returns returnedEntity
coEvery { recipeImageUrlProvider.generateImageUrl(eq("1")) } returns "imageUrl"
val expected = RecipeInfoUiState(
showIngredients = true,
showInstructions = true,
summaryEntity = FULL_CAKE_INFO_ENTITY.recipeSummaryEntity,
recipeIngredients = FULL_CAKE_INFO_ENTITY.recipeIngredients,
recipeInstructions = FULL_CAKE_INFO_ENTITY.recipeInstructions,
recipeInstructions = mapOf(
MIX_CAKE_RECIPE_INSTRUCTION_ENTITY to listOf(
CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY,
CAKE_BREAD_RECIPE_INGREDIENT_ENTITY,
),
BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY to emptyList(),
),
title = FULL_CAKE_INFO_ENTITY.recipeSummaryEntity.name,
description = FULL_CAKE_INFO_ENTITY.recipeSummaryEntity.description,
imageUrl = "imageUrl",
)
val actual = createSubject().uiState.asFlow().first()
val actual = createSubject().uiState.first()
assertThat(actual).isEqualTo(expected)
}
private fun createSubject(): RecipeInfoViewModel {
val argument = RecipeInfoFragmentArgs(RECIPE_ID).toSavedStateHandle()
return RecipeInfoViewModel(recipeRepo, logger, argument)
return RecipeInfoViewModel(recipeRepo, logger, recipeImageUrlProvider, argument)
}
companion object {