Implement editing shopping items

This commit is contained in:
Reimar 2025-05-13 21:15:06 +02:00
parent b04f385bdb
commit 7a25b0708b
Signed by: Reimar
GPG Key ID: 93549FA07F0AE268
7 changed files with 120 additions and 70 deletions

View File

@ -8,13 +8,12 @@ fun getShoppingList(ctx: Context): Array<ShoppingListItem> {
return requestJson<Unit, Array<ShoppingListItem>>(ctx, "GET", "/api/ShoppingList/get", null)
}
@Serializable
data class AddShoppingItemRequest(val name: String, val amount: Double?, val unit: String?, val checked: Boolean)
fun addShoppingItem(ctx: Context, item: ShoppingListItem): ShoppingListItem {
return requestJson<ShoppingListItem, ShoppingListItem>(ctx, "POST", "/api/ShoppingList/add", item)
}
fun addShoppingItem(ctx: Context, name: String, amount: Double?, unit: String?): ShoppingListItem {
val request = AddShoppingItemRequest(name, amount, unit, false)
return requestJson<AddShoppingItemRequest, ShoppingListItem>(ctx, "POST", "/api/ShoppingList/add", request)
fun editShoppingItem(ctx: Context, old: ShoppingListItem, new: ShoppingListItem) {
requestJson<ShoppingListItem, Boolean>(ctx, "PUT", "/api/ShoppingList/update?itemId=${old.id}", new)
}
fun toggleShoppingItemChecked(ctx: Context, item: ShoppingListItem) {

View File

@ -3,4 +3,10 @@ package tech.mercantec.easyeat.models
import kotlinx.serialization.Serializable
@Serializable
data class ShoppingListItem(val id: Int, var name: String, var amount: Double?, var unit: String?, var checked: Boolean)
data class ShoppingListItem(
var id: Int? = null,
var name: String,
var amount: Double?,
var unit: String?,
var checked: Boolean = false,
)

View File

@ -9,6 +9,7 @@ import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.PopupMenu
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import tech.mercantec.easyeat.R
@ -16,6 +17,7 @@ import tech.mercantec.easyeat.databinding.FragmentShoppingListBinding
import tech.mercantec.easyeat.helpers.ApiRequestException
import tech.mercantec.easyeat.helpers.addShoppingItem
import tech.mercantec.easyeat.helpers.deleteShoppingItem
import tech.mercantec.easyeat.helpers.editShoppingItem
import tech.mercantec.easyeat.helpers.getShoppingList
import tech.mercantec.easyeat.helpers.toggleShoppingItemChecked
import tech.mercantec.easyeat.models.ShoppingListItem
@ -86,11 +88,36 @@ class ShoppingListFragment : Fragment() {
val popup = PopupMenu(requireActivity(), view)
popup.apply {
menuInflater.inflate(R.menu.shopping_item_context_menu, menu)
setOnMenuItemClickListener {
when (it.itemId) {
R.id.edit_shopping_item -> {
showEditDialog(item) { dialog, newItem ->
thread {
try {
editShoppingItem(requireContext(), item, newItem)
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
return@thread
} finally {
dialog.dismiss()
}
activity?.runOnUiThread {
val adapter = binding.shoppingList.adapter as ShoppingItemAdapter
adapter.remove(item)
adapter.insert(newItem, position)
}
}
}
true
}
R.id.remove_shopping_item -> {
(parent.adapter as ShoppingItemAdapter).remove(item)
@ -119,65 +146,88 @@ class ShoppingListFragment : Fragment() {
// Show new item dialog when clicking add
binding.addToShoppingList.setOnClickListener {
val view = requireActivity().layoutInflater.inflate(R.layout.dialog_add_to_shopping_list, null)
val dialog = AlertDialog.Builder(activity)
.setView(view)
.setPositiveButton(R.string.add_label) { dialog, id ->
val dialog = dialog as AlertDialog
val amount = view.findViewById<EditText>(R.id.amount).text.toString().toDouble()
val unit = view.findViewById<Spinner>(R.id.unit_selector).selectedItem.toString().ifEmpty { null }
val name = view.findViewById<EditText>(R.id.name).text.toString()
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false
thread {
val item: ShoppingListItem
try {
item = addShoppingItem(requireContext(), name, amount, unit)
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
return@thread
} finally {
activity?.runOnUiThread {
dialog.dismiss()
}
showEditDialog(null) { dialog, item ->
thread {
val newItem: ShoppingListItem
try {
newItem = addShoppingItem(requireContext(), item)
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
return@thread
} finally {
activity?.runOnUiThread {
val adapter = binding.shoppingList.adapter as ShoppingItemAdapter
for (i in 0 ..< adapter.count) {
if (adapter.getItem(i)?.id == item.id) {
adapter.remove(adapter.getItem(i))
adapter.insert(item, i)
return@runOnUiThread
}
}
adapter.insert(item, adapter.count)
dialog.dismiss()
}
}
activity?.runOnUiThread {
val adapter = binding.shoppingList.adapter as ShoppingItemAdapter
for (i in 0..<adapter.count) {
if (adapter.getItem(i)?.id == newItem.id) {
adapter.remove(adapter.getItem(i))
adapter.insert(newItem, i)
return@runOnUiThread
}
}
adapter.insert(newItem, adapter.count)
}
}
.setNegativeButton(R.string.cancel_label) { dialog, _ ->
dialog.cancel()
}
.create()
dialog.show()
val adapter = ArrayAdapter.createFromResource(requireContext(), R.array.units, android.R.layout.simple_spinner_item)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
dialog.findViewById<Spinner>(R.id.unit_selector).adapter = adapter
}
}
return binding.root
}
private fun showEditDialog(item: ShoppingListItem?, onSave: (dialog: AlertDialog, item: ShoppingListItem) -> Unit) {
val view = requireActivity().layoutInflater.inflate(R.layout.dialog_edit_shopping_item, null)
val dialog = AlertDialog.Builder(activity)
.setView(view)
.setPositiveButton(R.string.add_label) { dialog, id ->
val dialog = dialog as AlertDialog
val amount = view.findViewById<EditText>(R.id.amount).text.toString().toDouble()
val unit = view.findViewById<Spinner>(R.id.unit_selector).selectedItem.toString()
.ifEmpty { null }
val name = view.findViewById<EditText>(R.id.name).text.toString()
onSave(dialog, ShoppingListItem(item?.id, name, amount, unit, item?.checked ?: false))
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false
}
.setNegativeButton(R.string.cancel_label) { dialog, _ ->
dialog.cancel()
}
.create()
dialog.show()
val adapter = ArrayAdapter.createFromResource(
requireContext(),
R.array.units,
android.R.layout.simple_spinner_item
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
dialog.findViewById<Spinner>(R.id.unit_selector).adapter = adapter
// Pre-fill dialog inputs with current item if applicable
item?.let { item ->
view.findViewById<EditText>(R.id.amount).setText(item.amount?.toBigDecimal()?.stripTrailingZeros().toString(), TextView.BufferType.EDITABLE)
view.findViewById<Spinner>(R.id.unit_selector).setSelection(adapter.getPosition(item.unit))
view.findViewById<EditText>(R.id.name).setText(item.name, TextView.BufferType.EDITABLE)
view.findViewById<TextView>(R.id.title).text = resources.getString(R.string.edit_shopping_item_label)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).text = resources.getString(R.string.save_label)
}
}
}

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M280,840Q247,840 223.5,816.5Q200,793 200,760L200,240L160,240L160,160L360,160L360,120L600,120L600,160L800,160L800,240L760,240L760,760Q760,793 736.5,816.5Q713,840 680,840L280,840ZM360,680L440,680L440,320L360,320L360,680ZM520,680L600,680L600,320L520,320L520,680Z"/>
</vector>

View File

@ -1,8 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/edit_shopping_item"
android:title="@string/edit_label"
/>
<item
android:id="@+id/remove_shopping_item"
android:icon="@drawable/ic_delete_24px"
android:title="@string/delete_label"
/>
</menu>

View File

@ -32,6 +32,7 @@
<string name="Instructions">Instructions</string>
<string name="cancel_label">Cancel</string>
<string name="add_shopping_item_label">Add shopping item</string>
<string name="edit_shopping_item_label">Edit shopping item</string>
<string name="ingredient_amount_label">Amount</string>
<string name="ingredient_amount_hint">500</string>
<string name="ingredient_name_label">Name</string>
@ -40,6 +41,7 @@
<string name="logout_label">Log out</string>
<string name="checked_desc">Checked</string>
<string name="delete_label">Delete</string>
<string name="edit_label">Edit</string>
<string name="empty_shopping_list">Your shopping list is empty</string>
<string name="create_dish">Create Dish</string>
<string name="ai_generate_recipe_title">Generate recipe with AI</string>