Implement adding and modifying shopping list items (#165)

* Add dismissed shopping list item preview

* Implement editing of note and quantity

* Add new editor row for food

* Implement loading units and foods

* Display dropdown for foods

* Display dropdown for units

* Implement updating food and units

* Create secondary editor state constructor

* Display "Add" button

* Combine editing state to an object

* Implement showing editor for new items

* Implement saving new items

* Log final screen state

* Fix ordering of foods

* Show keyboard when editing starts

* Add bottom padding to the list

* Show new items above checked
This commit is contained in:
Kirill Kamakin
2023-07-22 18:02:45 +02:00
committed by GitHub
parent 3ae784df97
commit be51a5c00a
27 changed files with 1020 additions and 118 deletions

View File

@@ -0,0 +1,6 @@
package gq.kirmanak.mealient.datasource.models
data class FoodInfo(
val name: String,
val id: String
)

View File

@@ -14,8 +14,8 @@ data class ShoppingListItemInfo(
val isFood: Boolean,
val note: String,
val quantity: Double,
val unit: String,
val food: String,
val unit: UnitInfo?,
val food: FoodInfo?,
val recipeReferences: List<ShoppingListItemRecipeReferenceInfo>,
)

View File

@@ -0,0 +1,11 @@
package gq.kirmanak.mealient.datasource.models
data class NewShoppingListItemInfo(
val shoppingListId: String,
val isFood: Boolean,
val note: String,
val quantity: Double,
val unit: UnitInfo?,
val food: FoodInfo?,
val position: Int,
)

View File

@@ -0,0 +1,6 @@
package gq.kirmanak.mealient.datasource.models
data class UnitInfo(
val name: String,
val id: String
)

View File

@@ -1,12 +1,16 @@
package gq.kirmanak.mealient.datasource.v1
import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo
import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenRequestV1
import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenResponseV1
import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
import gq.kirmanak.mealient.datasource.v1.models.CreateShoppingListItemRequestV1
import gq.kirmanak.mealient.datasource.v1.models.GetFoodsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetUnitsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetUserInfoResponseV1
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
@@ -63,7 +67,13 @@ interface MealieDataSourceV1 {
suspend fun getShoppingList(id: String): GetShoppingListResponseV1
suspend fun updateIsShoppingListItemChecked(id: String, isChecked: Boolean)
suspend fun deleteShoppingListItem(id: String)
suspend fun updateShoppingListItem(item: ShoppingListItemInfo)
suspend fun getFoods(): GetFoodsResponseV1
suspend fun getUnits(): GetUnitsResponseV1
suspend fun addShoppingListItem(request: CreateShoppingListItemRequestV1)
}

View File

@@ -3,14 +3,18 @@ package gq.kirmanak.mealient.datasource.v1
import gq.kirmanak.mealient.datasource.NetworkError
import gq.kirmanak.mealient.datasource.NetworkRequestWrapper
import gq.kirmanak.mealient.datasource.decode
import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo
import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenRequestV1
import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenResponseV1
import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1
import gq.kirmanak.mealient.datasource.v1.models.CreateShoppingListItemRequestV1
import gq.kirmanak.mealient.datasource.v1.models.ErrorDetailV1
import gq.kirmanak.mealient.datasource.v1.models.GetFoodsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetUnitsResponseV1
import gq.kirmanak.mealient.datasource.v1.models.GetUserInfoResponseV1
import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1
import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1
@@ -20,9 +24,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import retrofit2.HttpException
import java.net.ConnectException
import java.net.SocketTimeoutException
@@ -175,20 +177,6 @@ class MealieDataSourceV1Impl @Inject constructor(
logParameters = { "id = $id, request = $request" }
)
override suspend fun updateIsShoppingListItemChecked(
id: String,
isChecked: Boolean
) {
// Has to be done in two steps because the API doesn't support updating the checked state
val item = getShoppingListItem(id)
val wasChecked = item.jsonObject.getValue("checked").jsonPrimitive.boolean
if (wasChecked == isChecked) return
val updatedItem = item.jsonObject.toMutableMap().apply {
put("checked", JsonPrimitive(isChecked))
}
updateShoppingListItem(id, JsonObject(updatedItem))
}
override suspend fun deleteShoppingListItem(
id: String,
) = networkRequestWrapper.makeCallAndHandleUnauthorized(
@@ -196,4 +184,44 @@ class MealieDataSourceV1Impl @Inject constructor(
logMethod = { "deleteShoppingListItem" },
logParameters = { "id = $id" }
)
override suspend fun updateShoppingListItem(
item: ShoppingListItemInfo
) {
// Has to be done in two steps because we can't specify only the changed fields
val remoteItem = getShoppingListItem(item.id)
val updatedItem = remoteItem.jsonObject.toMutableMap().apply {
put("checked", JsonPrimitive(item.checked))
put("isFood", JsonPrimitive(item.isFood))
put("note", JsonPrimitive(item.note))
put("quantity", JsonPrimitive(item.quantity))
put("foodId", JsonPrimitive(item.food?.id))
put("unitId", JsonPrimitive(item.unit?.id))
remove("unit")
remove("food")
}
updateShoppingListItem(item.id, JsonObject(updatedItem))
}
override suspend fun getFoods(): GetFoodsResponseV1 {
return networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.getFoods(perPage = -1) },
logMethod = { "getFoods" },
)
}
override suspend fun getUnits(): GetUnitsResponseV1 {
return networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.getUnits(perPage = -1) },
logMethod = { "getUnits" },
)
}
override suspend fun addShoppingListItem(
request: CreateShoppingListItemRequestV1
) = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.createShoppingListItem(request) },
logMethod = { "addShoppingListItem" },
logParameters = { "request = $request" }
)
}

View File

@@ -94,4 +94,19 @@ interface MealieServiceV1 {
suspend fun deleteShoppingListItem(
@Path("id") id: String,
)
@GET("/api/foods")
suspend fun getFoods(
@Query("perPage") perPage: Int,
): GetFoodsResponseV1
@GET("/api/units")
suspend fun getUnits(
@Query("perPage") perPage: Int,
): GetUnitsResponseV1
@POST("/api/groups/shopping/items")
suspend fun createShoppingListItem(
@Body request: CreateShoppingListItemRequestV1,
)
}

View File

@@ -0,0 +1,16 @@
package gq.kirmanak.mealient.datasource.v1.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class CreateShoppingListItemRequestV1(
@SerialName("shopping_list_id") val shoppingListId: String,
@SerialName("checked") val checked: Boolean,
@SerialName("position") val position: Int?,
@SerialName("is_food") val isFood: Boolean,
@SerialName("note") val note: String,
@SerialName("quantity") val quantity: Double,
@SerialName("food_id") val foodId: String?,
@SerialName("unit_id") val unitId: String?,
)

View File

@@ -0,0 +1,15 @@
package gq.kirmanak.mealient.datasource.v1.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GetFoodsResponseV1(
@SerialName("items") val items: List<GetFoodResponseV1>,
)
@Serializable
data class GetFoodResponseV1(
@SerialName("name") val name: String,
@SerialName("id") val id: String,
)

View File

@@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class GetRecipeIngredientFoodResponseV1(
@SerialName("name") val name: String = "",
@SerialName("id") val id: String = "",
)

View File

@@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class GetRecipeIngredientUnitResponseV1(
@SerialName("name") val name: String = "",
@SerialName("id") val id: String = "",
)

View File

@@ -0,0 +1,15 @@
package gq.kirmanak.mealient.datasource.v1.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GetUnitsResponseV1(
@SerialName("items") val items: List<GetUnitResponseV1>
)
@Serializable
data class GetUnitResponseV1(
@SerialName("name") val name: String,
@SerialName("id") val id: String
)