Implement shopping lists screen (#129)

* Initialize shopping lists feature

* Start shopping lists screen with Compose

* Add icon to shopping list name

* Add shopping lists to menu

* Set max size for the list

* Replace compose-adapter with accompanist

* Remove unused fields from shopping lists response

* Show list of shopping lists from BE

* Hide shopping lists if Mealie is 0.5.6

* Add shopping list item click listener

* Create material app theme for Compose

* Use shorter names

* Load shopping lists by pages and save to db

* Make page handling logic match recipes

* Add swipe to refresh to shopping lists

* Extract SwipeToRefresh Composable

* Make LazyPagingColumn generic

* Show refresh only when mediator is refreshing

* Do not refresh automatically

* Allow controlling Activity state from modules

* Implement navigating to shopping list screen

* Move Compose libraries setup to a plugin

* Implement loading full shopping list info

* Move Storage classes to database module

* Save shopping list items to DB

* Use separate names for separate ids

* Do only one DB version update

* Use unique names for all columns

* Display shopping list items

* Move OperationUiState to ui module

* Subscribe to shopping lists updates

* Indicate progress with progress bar

* Use strings from resources

* Format shopping list item quantities

* Hide unit/food/note/quantity if they are not set

* Implement updating shopping list item checked state

* Remove unnecessary null checks

* Disable checkbox when it is being updated

* Split shopping list screen into composables

* Show items immediately if they are saved

* Fix showing "list is empty" before the items

* Show Snackbar when error happens

* Reduce shopping list items paddings

* Remove shopping lists when URL is changed

* Add error/empty state handling to shopping lists

* Fix empty error state

* Fix tests compilation

* Add margin between text and button

* Add divider between checked and unchecked items

* Move divider to the item

* Refresh the shopping lists on authentication

* Use retry when necessary

* Remove excessive logging

* Fix pages bounds check

* Move FlowExtensionsTest

* Update Compose version

* Fix showing loading indicator for shopping lists

* Add Russian translation

* Fix SDK version lint check

* Rename parameter to match interface

* Add DB migration TODO

* Get rid of DB migrations

* Do not use pagination with shopping lists

* Cleanup after the pagination removal

* Load shopping list items

* Remove shopping lists storage

* Rethrow CancellationException in LoadingHelper

* Add pull-to-refresh on shopping list screen

* Extract LazyColumnWithLoadingState

* Split refresh errors and loading state

* Reuse LazyColumnWithLoadingState for shopping list items

* Remove paging-compose dependency

* Refresh shopping list items on authentication

* Disable missing translation lint check

* Update Compose and Kotlin versions

* Fix order of checked items

* Hide useless information from a shopping list item
This commit is contained in:
Kirill Kamakin
2023-07-03 15:07:19 +02:00
committed by GitHub
parent a40f9a78ea
commit 1e5e727e92
157 changed files with 3360 additions and 3715 deletions

View File

@@ -0,0 +1,130 @@
package gq.kirmanak.mealient.model_mapper
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.models.AddRecipeInfo
import gq.kirmanak.mealient.datasource.models.AddRecipeIngredientInfo
import gq.kirmanak.mealient.datasource.models.AddRecipeInstructionInfo
import gq.kirmanak.mealient.datasource.models.AddRecipeSettingsInfo
import gq.kirmanak.mealient.datasource.models.FullRecipeInfo
import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo
import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo
import gq.kirmanak.mealient.datasource.models.RecipeIngredientInfo
import gq.kirmanak.mealient.datasource.models.RecipeInstructionInfo
import gq.kirmanak.mealient.datasource.models.RecipeSettingsInfo
import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo
import gq.kirmanak.mealient.datasource.models.ShoppingListInfo
import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo
import gq.kirmanak.mealient.datasource.models.ShoppingListItemRecipeReferenceInfo
import gq.kirmanak.mealient.datasource.models.ShoppingListsInfo
import gq.kirmanak.mealient.datasource.models.VersionInfo
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.ParseRecipeURLRequestV0
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.GetShoppingListItemRecipeReferenceFullResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListItemResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsSummaryResponseV1
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
interface ModelMapper {
fun toRecipeEntity(fullRecipeInfo: FullRecipeInfo): RecipeEntity
fun toRecipeIngredientEntity(
recipeIngredientInfo: RecipeIngredientInfo, remoteId: String
): RecipeIngredientEntity
fun toRecipeInstructionEntity(
recipeInstructionInfo: RecipeInstructionInfo, remoteId: String
): RecipeInstructionEntity
fun toRecipeSummaryEntity(
recipeSummaryInfo: RecipeSummaryInfo, isFavorite: Boolean
): RecipeSummaryEntity
fun toAddRecipeInfo(addRecipeDraft: AddRecipeDraft): AddRecipeInfo
fun toDraft(addRecipeInfo: AddRecipeInfo): AddRecipeDraft
fun toVersionInfo(versionResponseV1: VersionResponseV1): VersionInfo
fun toFullRecipeInfo(getRecipeResponseV1: GetRecipeResponseV1): FullRecipeInfo
fun toRecipeSettingsInfo(getRecipeSettingsResponseV1: GetRecipeSettingsResponseV1?): RecipeSettingsInfo
fun toRecipeIngredientInfo(getRecipeIngredientResponseV1: GetRecipeIngredientResponseV1): RecipeIngredientInfo
fun toRecipeInstructionInfo(getRecipeInstructionResponseV1: GetRecipeInstructionResponseV1): RecipeInstructionInfo
fun toV1CreateRequest(addRecipeInfo: AddRecipeInfo): CreateRecipeRequestV1
fun toV1UpdateRequest(addRecipeInfo: AddRecipeInfo): UpdateRecipeRequestV1
fun toV1Settings(addRecipeSettingsInfo: AddRecipeSettingsInfo): AddRecipeSettingsV1
fun toV1Ingredient(addRecipeIngredientInfo: AddRecipeIngredientInfo): AddRecipeIngredientV1
fun toV1Instruction(addRecipeInstructionInfo: AddRecipeInstructionInfo): AddRecipeInstructionV1
fun toV1Request(parseRecipeURLInfo: ParseRecipeURLInfo): ParseRecipeURLRequestV1
fun toFullShoppingListInfo(getShoppingListResponseV1: GetShoppingListResponseV1): FullShoppingListInfo
fun toShoppingListItemInfo(
getShoppingListItemResponseV1: GetShoppingListItemResponseV1,
recipes: Map<String, List<GetShoppingListItemRecipeReferenceFullResponseV1>>
): ShoppingListItemInfo
fun toShoppingListItemRecipeReferenceInfo(
getShoppingListItemRecipeReferenceFullResponseV1: GetShoppingListItemRecipeReferenceFullResponseV1
): ShoppingListItemRecipeReferenceInfo
fun toShoppingListsInfo(getShoppingListsResponseV1: GetShoppingListsResponseV1): ShoppingListsInfo
fun toShoppingListInfo(getShoppingListsSummaryResponseV1: GetShoppingListsSummaryResponseV1): ShoppingListInfo
fun toRecipeSummaryInfo(getRecipeSummaryResponseV1: GetRecipeSummaryResponseV1): RecipeSummaryInfo
fun toRecipeSummaryInfo(getRecipeSummaryResponseV0: GetRecipeSummaryResponseV0): RecipeSummaryInfo
fun toVersionInfo(versionResponseV0: VersionResponseV0): VersionInfo
fun toFullRecipeInfo(getRecipeResponseV0: GetRecipeResponseV0): FullRecipeInfo
fun toRecipeIngredientInfo(getRecipeIngredientResponseV0: GetRecipeIngredientResponseV0): RecipeIngredientInfo
fun toRecipeInstructionInfo(getRecipeInstructionResponseV0: GetRecipeInstructionResponseV0): RecipeInstructionInfo
fun toV0Request(addRecipeInfo: AddRecipeInfo): AddRecipeRequestV0
fun toV0Settings(addRecipeSettingsInfo: AddRecipeSettingsInfo): AddRecipeSettingsV0
fun toV0Ingredient(addRecipeIngredientInfo: AddRecipeIngredientInfo): AddRecipeIngredientV0
fun toV0Instruction(addRecipeInstructionInfo: AddRecipeInstructionInfo): AddRecipeInstructionV0
fun toV0Request(parseRecipeURLInfo: ParseRecipeURLInfo): ParseRecipeURLRequestV0
}

View File

@@ -0,0 +1,310 @@
package gq.kirmanak.mealient.model_mapper
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.models.AddRecipeInfo
import gq.kirmanak.mealient.datasource.models.AddRecipeIngredientInfo
import gq.kirmanak.mealient.datasource.models.AddRecipeInstructionInfo
import gq.kirmanak.mealient.datasource.models.AddRecipeSettingsInfo
import gq.kirmanak.mealient.datasource.models.FullRecipeInfo
import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo
import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo
import gq.kirmanak.mealient.datasource.models.RecipeIngredientInfo
import gq.kirmanak.mealient.datasource.models.RecipeInstructionInfo
import gq.kirmanak.mealient.datasource.models.RecipeSettingsInfo
import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo
import gq.kirmanak.mealient.datasource.models.ShoppingListInfo
import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo
import gq.kirmanak.mealient.datasource.models.ShoppingListItemRecipeReferenceInfo
import gq.kirmanak.mealient.datasource.models.ShoppingListsInfo
import gq.kirmanak.mealient.datasource.models.VersionInfo
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.ParseRecipeURLRequestV0
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.GetShoppingListItemRecipeReferenceFullResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListItemResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsSummaryResponseV1
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ModelMapperImpl @Inject constructor() : ModelMapper {
override fun toRecipeEntity(fullRecipeInfo: FullRecipeInfo) = RecipeEntity(
remoteId = fullRecipeInfo.remoteId,
recipeYield = fullRecipeInfo.recipeYield,
disableAmounts = fullRecipeInfo.settings.disableAmounts,
)
override fun toRecipeIngredientEntity(
recipeIngredientInfo: RecipeIngredientInfo, remoteId: String
) = RecipeIngredientEntity(
recipeId = remoteId,
note = recipeIngredientInfo.note,
unit = recipeIngredientInfo.unit,
food = recipeIngredientInfo.food,
quantity = recipeIngredientInfo.quantity,
title = recipeIngredientInfo.title,
)
override fun toRecipeInstructionEntity(
recipeInstructionInfo: RecipeInstructionInfo, remoteId: String
) = RecipeInstructionEntity(
recipeId = remoteId, text = recipeInstructionInfo.text
)
override fun toRecipeSummaryEntity(recipeSummaryInfo: RecipeSummaryInfo, isFavorite: Boolean) =
RecipeSummaryEntity(
remoteId = recipeSummaryInfo.remoteId,
name = recipeSummaryInfo.name,
slug = recipeSummaryInfo.slug,
description = recipeSummaryInfo.description,
dateAdded = recipeSummaryInfo.dateAdded,
dateUpdated = recipeSummaryInfo.dateUpdated,
imageId = recipeSummaryInfo.imageId,
isFavorite = isFavorite,
)
override fun toAddRecipeInfo(addRecipeDraft: AddRecipeDraft) = AddRecipeInfo(
name = addRecipeDraft.recipeName,
description = addRecipeDraft.recipeDescription,
recipeYield = addRecipeDraft.recipeYield,
recipeIngredient = addRecipeDraft.recipeIngredients.map { AddRecipeIngredientInfo(note = it) },
recipeInstructions = addRecipeDraft.recipeInstructions.map { AddRecipeInstructionInfo(text = it) },
settings = AddRecipeSettingsInfo(
public = addRecipeDraft.isRecipePublic,
disableComments = addRecipeDraft.areCommentsDisabled,
)
)
override fun toDraft(addRecipeInfo: AddRecipeInfo): AddRecipeDraft = AddRecipeDraft(
recipeName = addRecipeInfo.name,
recipeDescription = addRecipeInfo.description,
recipeYield = addRecipeInfo.recipeYield,
recipeInstructions = addRecipeInfo.recipeInstructions.map { it.text },
recipeIngredients = addRecipeInfo.recipeIngredient.map { it.note },
isRecipePublic = addRecipeInfo.settings.public,
areCommentsDisabled = addRecipeInfo.settings.disableComments,
)
override fun toVersionInfo(versionResponseV1: VersionResponseV1) =
VersionInfo(versionResponseV1.version)
override fun toFullRecipeInfo(getRecipeResponseV1: GetRecipeResponseV1) = FullRecipeInfo(
remoteId = getRecipeResponseV1.remoteId,
name = getRecipeResponseV1.name,
recipeYield = getRecipeResponseV1.recipeYield,
recipeIngredients = getRecipeResponseV1.recipeIngredients.map { toRecipeIngredientInfo(it) },
recipeInstructions = getRecipeResponseV1.recipeInstructions.map { toRecipeInstructionInfo(it) },
settings = toRecipeSettingsInfo(getRecipeResponseV1.settings),
)
override fun toRecipeSettingsInfo(getRecipeSettingsResponseV1: GetRecipeSettingsResponseV1?) =
RecipeSettingsInfo(
disableAmounts = getRecipeSettingsResponseV1?.disableAmount ?: true,
)
override fun toRecipeIngredientInfo(getRecipeIngredientResponseV1: GetRecipeIngredientResponseV1) =
RecipeIngredientInfo(
note = getRecipeIngredientResponseV1.note,
unit = getRecipeIngredientResponseV1.unit?.name,
food = getRecipeIngredientResponseV1.food?.name,
quantity = getRecipeIngredientResponseV1.quantity,
title = getRecipeIngredientResponseV1.title,
)
override fun toRecipeInstructionInfo(getRecipeInstructionResponseV1: GetRecipeInstructionResponseV1) =
RecipeInstructionInfo(
text = getRecipeInstructionResponseV1.text
)
override fun toV1CreateRequest(addRecipeInfo: AddRecipeInfo) = CreateRecipeRequestV1(
name = addRecipeInfo.name,
)
override fun toV1UpdateRequest(addRecipeInfo: AddRecipeInfo) = UpdateRecipeRequestV1(
description = addRecipeInfo.description,
recipeYield = addRecipeInfo.recipeYield,
recipeIngredient = addRecipeInfo.recipeIngredient.map { toV1Ingredient(it) },
recipeInstructions = addRecipeInfo.recipeInstructions.map { toV1Instruction(it) },
settings = toV1Settings(addRecipeInfo.settings),
)
override fun toV1Settings(addRecipeSettingsInfo: AddRecipeSettingsInfo) = AddRecipeSettingsV1(
disableComments = addRecipeSettingsInfo.disableComments,
public = addRecipeSettingsInfo.public,
)
override fun toV1Ingredient(addRecipeIngredientInfo: AddRecipeIngredientInfo) =
AddRecipeIngredientV1(
id = UUID.randomUUID().toString(),
note = addRecipeIngredientInfo.note,
)
override fun toV1Instruction(addRecipeInstructionInfo: AddRecipeInstructionInfo) =
AddRecipeInstructionV1(
id = UUID.randomUUID().toString(),
text = addRecipeInstructionInfo.text,
ingredientReferences = emptyList(),
)
override fun toV1Request(parseRecipeURLInfo: ParseRecipeURLInfo) = ParseRecipeURLRequestV1(
url = parseRecipeURLInfo.url,
includeTags = parseRecipeURLInfo.includeTags,
)
override fun toFullShoppingListInfo(getShoppingListResponseV1: GetShoppingListResponseV1): FullShoppingListInfo {
val recipes = getShoppingListResponseV1.recipeReferences.groupBy { it.recipeId }
return FullShoppingListInfo(
id = getShoppingListResponseV1.id,
name = getShoppingListResponseV1.name,
items = getShoppingListResponseV1.listItems.map { toShoppingListItemInfo(it, recipes) },
)
}
override fun toShoppingListItemInfo(
getShoppingListItemResponseV1: GetShoppingListItemResponseV1,
recipes: Map<String, List<GetShoppingListItemRecipeReferenceFullResponseV1>>
): ShoppingListItemInfo = ShoppingListItemInfo(
shoppingListId = getShoppingListItemResponseV1.shoppingListId,
id = getShoppingListItemResponseV1.id,
checked = getShoppingListItemResponseV1.checked,
position = getShoppingListItemResponseV1.position,
isFood = getShoppingListItemResponseV1.isFood,
note = getShoppingListItemResponseV1.note,
quantity = getShoppingListItemResponseV1.quantity,
unit = getShoppingListItemResponseV1.unit?.name.orEmpty(),
food = getShoppingListItemResponseV1.food?.name.orEmpty(),
recipeReferences = getShoppingListItemResponseV1.recipeReferences.map { it.recipeId }
.mapNotNull { recipes[it] }.flatten().map { toShoppingListItemRecipeReferenceInfo(it) },
)
override fun toShoppingListItemRecipeReferenceInfo(
getShoppingListItemRecipeReferenceFullResponseV1: GetShoppingListItemRecipeReferenceFullResponseV1
) = ShoppingListItemRecipeReferenceInfo(
recipeId = getShoppingListItemRecipeReferenceFullResponseV1.recipeId,
recipeQuantity = getShoppingListItemRecipeReferenceFullResponseV1.recipeQuantity,
id = getShoppingListItemRecipeReferenceFullResponseV1.id,
shoppingListId = getShoppingListItemRecipeReferenceFullResponseV1.shoppingListId,
recipe = toFullRecipeInfo(getShoppingListItemRecipeReferenceFullResponseV1.recipe),
)
override fun toShoppingListsInfo(getShoppingListsResponseV1: GetShoppingListsResponseV1) =
ShoppingListsInfo(
page = getShoppingListsResponseV1.page,
perPage = getShoppingListsResponseV1.perPage,
totalPages = getShoppingListsResponseV1.totalPages,
totalItems = getShoppingListsResponseV1.total,
items = getShoppingListsResponseV1.items.map { toShoppingListInfo(it) },
)
override fun toShoppingListInfo(getShoppingListsSummaryResponseV1: GetShoppingListsSummaryResponseV1) =
ShoppingListInfo(
name = getShoppingListsSummaryResponseV1.name.orEmpty(),
id = getShoppingListsSummaryResponseV1.id,
)
override fun toRecipeSummaryInfo(getRecipeSummaryResponseV1: GetRecipeSummaryResponseV1) =
RecipeSummaryInfo(
remoteId = getRecipeSummaryResponseV1.remoteId,
name = getRecipeSummaryResponseV1.name,
slug = getRecipeSummaryResponseV1.slug,
description = getRecipeSummaryResponseV1.description,
dateAdded = getRecipeSummaryResponseV1.dateAdded,
dateUpdated = getRecipeSummaryResponseV1.dateUpdated,
imageId = getRecipeSummaryResponseV1.remoteId,
)
override fun toRecipeSummaryInfo(getRecipeSummaryResponseV0: GetRecipeSummaryResponseV0) =
RecipeSummaryInfo(
remoteId = getRecipeSummaryResponseV0.remoteId.toString(),
name = getRecipeSummaryResponseV0.name,
slug = getRecipeSummaryResponseV0.slug,
description = getRecipeSummaryResponseV0.description,
dateAdded = getRecipeSummaryResponseV0.dateAdded,
dateUpdated = getRecipeSummaryResponseV0.dateUpdated,
imageId = getRecipeSummaryResponseV0.slug,
)
override fun toVersionInfo(versionResponseV0: VersionResponseV0) =
VersionInfo(versionResponseV0.version)
override fun toFullRecipeInfo(getRecipeResponseV0: GetRecipeResponseV0) = FullRecipeInfo(
remoteId = getRecipeResponseV0.remoteId.toString(),
name = getRecipeResponseV0.name,
recipeYield = getRecipeResponseV0.recipeYield,
recipeIngredients = getRecipeResponseV0.recipeIngredients.map { toRecipeIngredientInfo(it) },
recipeInstructions = getRecipeResponseV0.recipeInstructions.map { toRecipeInstructionInfo(it) },
settings = RecipeSettingsInfo(disableAmounts = true)
)
override fun toRecipeIngredientInfo(getRecipeIngredientResponseV0: GetRecipeIngredientResponseV0) =
RecipeIngredientInfo(
note = getRecipeIngredientResponseV0.note,
unit = null,
food = null,
quantity = 1.0,
title = null,
)
override fun toRecipeInstructionInfo(getRecipeInstructionResponseV0: GetRecipeInstructionResponseV0) =
RecipeInstructionInfo(
text = getRecipeInstructionResponseV0.text
)
override fun toV0Request(addRecipeInfo: AddRecipeInfo) = AddRecipeRequestV0(
name = addRecipeInfo.name,
description = addRecipeInfo.description,
recipeYield = addRecipeInfo.recipeYield,
recipeIngredient = addRecipeInfo.recipeIngredient.map { toV0Ingredient(it) },
recipeInstructions = addRecipeInfo.recipeInstructions.map { toV0Instruction(it) },
settings = toV0Settings(addRecipeInfo.settings),
)
override fun toV0Settings(addRecipeSettingsInfo: AddRecipeSettingsInfo) = AddRecipeSettingsV0(
disableComments = addRecipeSettingsInfo.disableComments,
public = addRecipeSettingsInfo.public,
)
override fun toV0Ingredient(addRecipeIngredientInfo: AddRecipeIngredientInfo) =
AddRecipeIngredientV0(
note = addRecipeIngredientInfo.note,
)
override fun toV0Instruction(addRecipeInstructionInfo: AddRecipeInstructionInfo) =
AddRecipeInstructionV0(
text = addRecipeInstructionInfo.text,
)
override fun toV0Request(parseRecipeURLInfo: ParseRecipeURLInfo) = ParseRecipeURLRequestV0(
url = parseRecipeURLInfo.url,
)
}

View File

@@ -0,0 +1,16 @@
package gq.kirmanak.mealient.model_mapper
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
interface ModelMapperModule {
@Binds
@Singleton
fun bindModelMapper(impl: ModelMapperImpl): ModelMapper
}

View File

@@ -0,0 +1,157 @@
package gq.kirmanak.mealient.model_mapper
import com.google.common.truth.Truth.assertThat
import gq.kirmanak.mealient.database.CAKE_RECIPE_ENTITY
import gq.kirmanak.mealient.database.CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY
import gq.kirmanak.mealient.database.MIX_CAKE_RECIPE_INSTRUCTION_ENTITY
import gq.kirmanak.mealient.database.PORRIDGE_RECIPE_SUMMARY_ENTITY
import gq.kirmanak.mealient.datasource_test.CAKE_FULL_RECIPE_INFO
import gq.kirmanak.mealient.datasource_test.MILK_RECIPE_INGREDIENT_INFO
import gq.kirmanak.mealient.datasource_test.MILK_RECIPE_INGREDIENT_RESPONSE_V0
import gq.kirmanak.mealient.datasource_test.MILK_RECIPE_INGREDIENT_RESPONSE_V1
import gq.kirmanak.mealient.datasource_test.MIX_INSTRUCTION
import gq.kirmanak.mealient.datasource_test.MIX_RECIPE_INSTRUCTION_INFO
import gq.kirmanak.mealient.datasource_test.MIX_RECIPE_INSTRUCTION_RESPONSE_V0
import gq.kirmanak.mealient.datasource_test.MIX_RECIPE_INSTRUCTION_RESPONSE_V1
import gq.kirmanak.mealient.datasource_test.PORRIDGE_ADD_RECIPE_INFO
import gq.kirmanak.mealient.datasource_test.PORRIDGE_ADD_RECIPE_REQUEST_V0
import gq.kirmanak.mealient.datasource_test.PORRIDGE_CREATE_RECIPE_REQUEST_V1
import gq.kirmanak.mealient.datasource_test.PORRIDGE_FULL_RECIPE_INFO
import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_RESPONSE_V0
import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_RESPONSE_V1
import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0
import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1
import gq.kirmanak.mealient.datasource_test.PORRIDGE_UPDATE_RECIPE_REQUEST_V1
import gq.kirmanak.mealient.datasource_test.RECIPE_SUMMARY_PORRIDGE_V0
import gq.kirmanak.mealient.datasource_test.RECIPE_SUMMARY_PORRIDGE_V1
import gq.kirmanak.mealient.datasource_test.SUGAR_INGREDIENT
import gq.kirmanak.mealient.datasource_test.VERSION_INFO_V0
import gq.kirmanak.mealient.datasource_test.VERSION_INFO_V1
import gq.kirmanak.mealient.datasource_test.VERSION_RESPONSE_V0
import gq.kirmanak.mealient.datasource_test.VERSION_RESPONSE_V1
import gq.kirmanak.mealient.datastore_test.PORRIDGE_RECIPE_DRAFT
import gq.kirmanak.mealient.test.BaseUnitTest
import org.junit.Before
import org.junit.Test
class ModelMappingsTest : BaseUnitTest() {
private lateinit var subject: ModelMapper
@Before
override fun setUp() {
super.setUp()
subject = ModelMapperImpl()
}
@Test
fun `when toAddRecipeRequest then fills fields correctly`() {
assertThat(subject.toAddRecipeInfo(PORRIDGE_RECIPE_DRAFT)).isEqualTo(
PORRIDGE_ADD_RECIPE_INFO
)
}
@Test
fun `when toDraft then fills fields correctly`() {
assertThat(subject.toDraft(PORRIDGE_ADD_RECIPE_INFO)).isEqualTo(PORRIDGE_RECIPE_DRAFT)
}
@Test
fun `when full recipe info to entity expect correct entity`() {
assertThat(subject.toRecipeEntity(CAKE_FULL_RECIPE_INFO)).isEqualTo(CAKE_RECIPE_ENTITY)
}
@Test
fun `when ingredient info to entity expect correct entity`() {
val actual = subject.toRecipeIngredientEntity(SUGAR_INGREDIENT, "1")
assertThat(actual).isEqualTo(CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY)
}
@Test
fun `when instruction info to entity expect correct entity`() {
val actual = subject.toRecipeInstructionEntity(MIX_INSTRUCTION, "1")
assertThat(actual).isEqualTo(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY)
}
@Test
fun `when summary v0 to info expect correct info`() {
val actual = subject.toRecipeSummaryInfo(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0)
assertThat(actual).isEqualTo(RECIPE_SUMMARY_PORRIDGE_V0)
}
@Test
fun `when summary v1 to info expect correct info`() {
val actual = subject.toRecipeSummaryInfo(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1)
assertThat(actual).isEqualTo(RECIPE_SUMMARY_PORRIDGE_V1)
}
@Test
fun `when summary info to entity expect correct entity`() {
val actual = subject.toRecipeSummaryEntity(RECIPE_SUMMARY_PORRIDGE_V0, isFavorite = false)
assertThat(actual).isEqualTo(PORRIDGE_RECIPE_SUMMARY_ENTITY)
}
@Test
fun `when version response v0 to info expect correct info`() {
assertThat(subject.toVersionInfo(VERSION_RESPONSE_V0)).isEqualTo(VERSION_INFO_V0)
}
@Test
fun `when version response v1 to info expect correct info`() {
assertThat(subject.toVersionInfo(VERSION_RESPONSE_V1)).isEqualTo(VERSION_INFO_V1)
}
@Test
fun `when recipe ingredient response v0 to info expect correct info`() {
val actual = subject.toRecipeIngredientInfo(MILK_RECIPE_INGREDIENT_RESPONSE_V0)
assertThat(actual).isEqualTo(MILK_RECIPE_INGREDIENT_INFO)
}
@Test
fun `when recipe ingredient response v1 to info expect correct info`() {
val actual = subject.toRecipeIngredientInfo(MILK_RECIPE_INGREDIENT_RESPONSE_V1)
assertThat(actual).isEqualTo(MILK_RECIPE_INGREDIENT_INFO)
}
@Test
fun `when recipe instruction response v0 to info expect correct info`() {
val actual = subject.toRecipeInstructionInfo(MIX_RECIPE_INSTRUCTION_RESPONSE_V0)
assertThat(actual).isEqualTo(MIX_RECIPE_INSTRUCTION_INFO)
}
@Test
fun `when recipe instruction response v1 to info expect correct info`() {
val actual = subject.toRecipeInstructionInfo(MIX_RECIPE_INSTRUCTION_RESPONSE_V1)
assertThat(actual).isEqualTo(MIX_RECIPE_INSTRUCTION_INFO)
}
@Test
fun `when recipe response v0 to info expect correct info`() {
val actual = subject.toFullRecipeInfo(PORRIDGE_RECIPE_RESPONSE_V0)
assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO)
}
@Test
fun `when recipe response v1 to info expect correct info`() {
val actual = subject.toFullRecipeInfo(PORRIDGE_RECIPE_RESPONSE_V1)
assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO)
}
@Test
fun `when add recipe info to request v0 expect correct request`() {
val actual = subject.toV0Request(PORRIDGE_ADD_RECIPE_INFO)
assertThat(actual).isEqualTo(PORRIDGE_ADD_RECIPE_REQUEST_V0)
}
@Test
fun `when add recipe info to create request v1 expect correct request`() {
val actual = subject.toV1CreateRequest(PORRIDGE_ADD_RECIPE_INFO)
assertThat(actual).isEqualTo(PORRIDGE_CREATE_RECIPE_REQUEST_V1)
}
@Test
fun `when add recipe info to update request v1 expect correct request`() {
val actual = subject.toV1UpdateRequest(PORRIDGE_ADD_RECIPE_INFO)
assertThat(actual).isEqualTo(PORRIDGE_UPDATE_RECIPE_REQUEST_V1)
}
}