Implement the dialog to add a new shopping list (#280)
* Add endpoint to create new shopping lists * Initialize editing of lists names * Implement adding new lists * Fix invalid password for demo * Use StateFlow to avoid lost state updates * Refactor the list update to support empty lists * Hide add new list button if there's a new list * Scroll to the newly added list or item * Replace deprecated Divider * Move new field name input to dialog * Display a modal dialog instead of bottom sheet * Reduce unnecessary recompositions * Do not hide button since it is overlapped by dialog * Extract Composable for editable items * Remove unused imports * Add UI for removing and editing shopping lists * Implement editing list name and removing lists * Fix initial cursor state when editing name * Add capitalization of list names * Fix color of divider in dark mode
This commit is contained in:
@@ -4,6 +4,7 @@ import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateApiTokenResponse
|
||||
import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateShoppingListRequest
|
||||
import gq.kirmanak.mealient.datasource.models.GetFoodsResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
@@ -75,4 +76,10 @@ interface MealieDataSource {
|
||||
suspend fun getUnits(): GetUnitsResponse
|
||||
|
||||
suspend fun addShoppingListItem(request: CreateShoppingListItemRequest)
|
||||
|
||||
suspend fun addShoppingList(request: CreateShoppingListRequest)
|
||||
|
||||
suspend fun deleteShoppingList(id: String)
|
||||
|
||||
suspend fun updateShoppingListName(id: String, name: String)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateApiTokenResponse
|
||||
import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateShoppingListRequest
|
||||
import gq.kirmanak.mealient.datasource.models.GetFoodsResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipesResponse
|
||||
@@ -61,4 +62,12 @@ internal interface MealieService {
|
||||
suspend fun getUnits(perPage: Int): GetUnitsResponse
|
||||
|
||||
suspend fun createShoppingListItem(request: CreateShoppingListItemRequest)
|
||||
|
||||
suspend fun createShoppingList(request: CreateShoppingListRequest)
|
||||
|
||||
suspend fun deleteShoppingList(id: String)
|
||||
|
||||
suspend fun updateShoppingList(id: String, request: JsonElement)
|
||||
|
||||
suspend fun getShoppingListJson(id: String) : JsonElement
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateApiTokenResponse
|
||||
import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateShoppingListRequest
|
||||
import gq.kirmanak.mealient.datasource.models.ErrorDetail
|
||||
import gq.kirmanak.mealient.datasource.models.GetFoodsResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
@@ -37,7 +38,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
) : MealieDataSource {
|
||||
|
||||
override suspend fun createRecipe(
|
||||
recipe: CreateRecipeRequest
|
||||
recipe: CreateRecipeRequest,
|
||||
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.createRecipe(recipe) },
|
||||
logMethod = { "createRecipe" },
|
||||
@@ -46,7 +47,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
|
||||
override suspend fun updateRecipe(
|
||||
slug: String,
|
||||
recipe: UpdateRecipeRequest
|
||||
recipe: UpdateRecipeRequest,
|
||||
): GetRecipeResponse = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.updateRecipe(recipe, slug) },
|
||||
logMethod = { "updateRecipe" },
|
||||
@@ -80,7 +81,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
|
||||
override suspend fun requestRecipes(
|
||||
page: Int,
|
||||
perPage: Int
|
||||
perPage: Int,
|
||||
): List<GetRecipeSummaryResponse> = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.getRecipeSummary(page, perPage) },
|
||||
logMethod = { "requestRecipes" },
|
||||
@@ -88,7 +89,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
).items
|
||||
|
||||
override suspend fun requestRecipeInfo(
|
||||
slug: String
|
||||
slug: String,
|
||||
): GetRecipeResponse = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.getRecipe(slug) },
|
||||
logMethod = { "requestRecipeInfo" },
|
||||
@@ -96,7 +97,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
)
|
||||
|
||||
override suspend fun parseRecipeFromURL(
|
||||
request: ParseRecipeURLRequest
|
||||
request: ParseRecipeURLRequest,
|
||||
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.createRecipeFromURL(request) },
|
||||
logMethod = { "parseRecipeFromURL" },
|
||||
@@ -104,7 +105,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
)
|
||||
|
||||
override suspend fun createApiToken(
|
||||
request: CreateApiTokenRequest
|
||||
request: CreateApiTokenRequest,
|
||||
): CreateApiTokenResponse = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.createApiToken(request) },
|
||||
logMethod = { "createApiToken" },
|
||||
@@ -120,7 +121,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
|
||||
override suspend fun removeFavoriteRecipe(
|
||||
userId: String,
|
||||
recipeSlug: String
|
||||
recipeSlug: String,
|
||||
): Unit = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.removeFavoriteRecipe(userId, recipeSlug) },
|
||||
logMethod = { "removeFavoriteRecipe" },
|
||||
@@ -129,7 +130,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
|
||||
override suspend fun addFavoriteRecipe(
|
||||
userId: String,
|
||||
recipeSlug: String
|
||||
recipeSlug: String,
|
||||
): Unit = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.addFavoriteRecipe(userId, recipeSlug) },
|
||||
logMethod = { "addFavoriteRecipe" },
|
||||
@@ -137,7 +138,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
)
|
||||
|
||||
override suspend fun deleteRecipe(
|
||||
slug: String
|
||||
slug: String,
|
||||
): Unit = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.deleteRecipe(slug) },
|
||||
logMethod = { "deleteRecipe" },
|
||||
@@ -154,7 +155,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
)
|
||||
|
||||
override suspend fun getShoppingList(
|
||||
id: String
|
||||
id: String,
|
||||
): GetShoppingListResponse = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.getShoppingList(id) },
|
||||
logMethod = { "getShoppingList" },
|
||||
@@ -187,7 +188,7 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
)
|
||||
|
||||
override suspend fun updateShoppingListItem(
|
||||
item: GetShoppingListItemResponse
|
||||
item: GetShoppingListItemResponse,
|
||||
) {
|
||||
// Has to be done in two steps because we can't specify only the changed fields
|
||||
val remoteItem = getShoppingListItem(item.id)
|
||||
@@ -219,10 +220,55 @@ internal class MealieDataSourceImpl @Inject constructor(
|
||||
}
|
||||
|
||||
override suspend fun addShoppingListItem(
|
||||
request: CreateShoppingListItemRequest
|
||||
request: CreateShoppingListItemRequest,
|
||||
) = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.createShoppingListItem(request) },
|
||||
logMethod = { "addShoppingListItem" },
|
||||
logParameters = { "request = $request" }
|
||||
)
|
||||
|
||||
override suspend fun addShoppingList(
|
||||
request: CreateShoppingListRequest,
|
||||
) = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.createShoppingList(request) },
|
||||
logMethod = { "createShoppingList" },
|
||||
logParameters = { "request = $request" }
|
||||
)
|
||||
|
||||
private suspend fun updateShoppingList(
|
||||
id: String,
|
||||
request: JsonElement,
|
||||
) = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.updateShoppingList(id, request) },
|
||||
logMethod = { "updateShoppingList" },
|
||||
logParameters = { "id = $id, request = $request" }
|
||||
)
|
||||
|
||||
private suspend fun getShoppingListJson(
|
||||
id: String,
|
||||
) = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.getShoppingListJson(id) },
|
||||
logMethod = { "getShoppingListJson" },
|
||||
logParameters = { "id = $id" }
|
||||
)
|
||||
|
||||
override suspend fun deleteShoppingList(
|
||||
id: String,
|
||||
) = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.deleteShoppingList(id) },
|
||||
logMethod = { "deleteShoppingList" },
|
||||
logParameters = { "id = $id" }
|
||||
)
|
||||
|
||||
override suspend fun updateShoppingListName(
|
||||
id: String,
|
||||
name: String
|
||||
) {
|
||||
// Has to be done in two steps because we can't specify only the changed fields
|
||||
val remoteItem = getShoppingListJson(id)
|
||||
val updatedItem = remoteItem.jsonObject.toMutableMap().apply {
|
||||
put("name", JsonPrimitive(name))
|
||||
}.let(::JsonObject)
|
||||
updateShoppingList(id, updatedItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateApiTokenResponse
|
||||
import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest
|
||||
import gq.kirmanak.mealient.datasource.models.CreateShoppingListRequest
|
||||
import gq.kirmanak.mealient.datasource.models.GetFoodsResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipesResponse
|
||||
@@ -196,6 +197,34 @@ internal class MealieServiceKtor @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createShoppingList(request: CreateShoppingListRequest) {
|
||||
httpClient.post {
|
||||
endpoint("/api/groups/shopping/lists")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deleteShoppingList(id: String) {
|
||||
httpClient.delete {
|
||||
endpoint("/api/groups/shopping/lists/$id")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateShoppingList(id: String, request: JsonElement) {
|
||||
httpClient.put {
|
||||
endpoint("/api/groups/shopping/lists/$id")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getShoppingListJson(id: String): JsonElement {
|
||||
return httpClient.get {
|
||||
endpoint("/api/groups/shopping/lists/$id")
|
||||
}.body()
|
||||
}
|
||||
|
||||
private suspend fun HttpRequestBuilder.endpoint(
|
||||
path: String,
|
||||
block: URLBuilder.() -> Unit = {},
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package gq.kirmanak.mealient.datasource.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CreateShoppingListRequest(
|
||||
@SerialName("name") val name: String,
|
||||
)
|
||||
Reference in New Issue
Block a user