Implement deleting dish, use popup menu for edit and delete

This commit is contained in:
Reimar 2025-05-16 10:51:32 +02:00
parent 9c364b2493
commit 59683ea176
Signed by: Reimar
GPG Key ID: 93549FA07F0AE268
11 changed files with 102 additions and 150 deletions

View File

@ -19,7 +19,7 @@ class ApiRequestException(message: String, cause: Throwable?) : Exception(messag
class HttpResponse(val body: String, val code: Int)
fun request(ctx: Context, method: String, path: String, data: String?, autoRefresh: Boolean = true): HttpResponse {
val url = URL(BuildConfig.API_LOCALHOST_URL + path)
val url = URL(BuildConfig.API_BASE_URL + path)
val prefs = ctx.getSharedPreferences("easyeat", Context.MODE_PRIVATE)
val authToken = prefs.getString("auth-token", null)
@ -67,7 +67,7 @@ fun request(ctx: Context, method: String, path: String, data: String?, autoRefre
class HttpErrorResponse(val message: String)
@OptIn(ExperimentalSerializationApi::class)
val json = Json { explicitNulls = false }
val json = Json { explicitNulls = false } // Treat missing values as null when decoding
inline fun <reified Req, reified Res> requestJson(ctx: Context, method: String, path: String, data: Req?): Res {
val requestJson =

View File

@ -32,3 +32,6 @@ fun getRecipeDetails(ctx: Context, id: Int): Recipe {
return requestJson<Unit, Recipe>(ctx, "GET", "/api/Recipe/get/$id", null)
}
fun deleteRecipe(ctx: Context, id: Int) {
requestJson<Recipe, Boolean>(ctx, "DELETE", "/api/Recipe/delete/$id", null)
}

View File

@ -9,11 +9,7 @@ import android.widget.TextView
import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.models.DishListItem
class DishAdapter(
context: Context,
private val dishes: List<DishListItem>,
private val onDishClick: (DishListItem) -> Unit
) : ArrayAdapter<DishListItem>(context, 0, dishes) {
class DishAdapter(context: Context, private val dishes: List<DishListItem>) : ArrayAdapter<DishListItem>(context, 0, dishes) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val dish = getItem(position)
@ -25,10 +21,6 @@ class DishAdapter(
nameTextView.text = dish?.name
descriptionTextView.text = dish?.description
view.setOnClickListener {
dish?.let { onDishClick(it) }
}
return view
}
}

View File

@ -26,8 +26,6 @@ import tech.mercantec.easyeat.helpers.AddRecipeToShoppingList
import tech.mercantec.easyeat.models.Ingredient
import tech.mercantec.easyeat.models.Recipe
private lateinit var editRecipeLauncher: ActivityResultLauncher<Intent>
class DishDetailsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -40,12 +38,6 @@ class DishDetailsActivity : AppCompatActivity() {
return
}
editRecipeLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
loadRecipe(dishId)
}
}
findViewById<Button>(R.id.addDishToShoppingList).setOnClickListener {
val progressDialog = ProgressDialog(this)
progressDialog.setMessage("Loading...")
@ -82,7 +74,6 @@ class DishDetailsActivity : AppCompatActivity() {
val ingredientsContainer = findViewById<LinearLayout>(R.id.ingredients)
val multiplierEditText = findViewById<EditText>(R.id.ingredient_multiplier)
val instructionsLayout = findViewById<LinearLayout>(R.id.instructions)
val editRecipeBtn: Button = findViewById(R.id.editRecipeBtn)
thread {
val recipe: Recipe
@ -151,13 +142,6 @@ class DishDetailsActivity : AppCompatActivity() {
displayIngredients(recipe.ingredients, multiplier)
}
}
editRecipeBtn.setOnClickListener {
val jsonRecipe = Json.encodeToString(recipe)
val intent = Intent(this, EditDishActivity::class.java)
intent.putExtra("recipe_json", jsonRecipe)
editRecipeLauncher.launch(intent)
}
}
}
}

View File

@ -1,6 +1,7 @@
package tech.mercantec.easyeat.ui.dishes
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
@ -11,11 +12,15 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.databinding.FragmentDishesBinding
import tech.mercantec.easyeat.helpers.ApiRequestException
import tech.mercantec.easyeat.helpers.GetAllRecipesResponse
import tech.mercantec.easyeat.helpers.deleteRecipe
import tech.mercantec.easyeat.helpers.getAllRecipes
import tech.mercantec.easyeat.helpers.getRecipeDetails
import tech.mercantec.easyeat.models.DishListItem
import tech.mercantec.easyeat.models.Recipe
import kotlin.concurrent.thread
@ -23,7 +28,7 @@ import kotlin.concurrent.thread
class DishesFragment : Fragment() {
private var _binding: FragmentDishesBinding? = null
private lateinit var createDishLauncher: ActivityResultLauncher<Intent>
private lateinit var launcher: ActivityResultLauncher<Intent>
private val binding get() = _binding!!
override fun onCreateView(
@ -36,6 +41,79 @@ class DishesFragment : Fragment() {
loadRecipes()
// Show details on click
binding.dishesList.setOnItemClickListener { parent, view, position, id ->
val item = parent.getItemAtPosition(position) as DishListItem
val intent = Intent(requireContext(), DishDetailsActivity::class.java)
intent.putExtra("dish_id", item.id)
startActivity(intent)
}
// Show context menu when long clicking shopping item
binding.dishesList.setOnItemLongClickListener { parent, view, position, id ->
val item = parent.getItemAtPosition(position) as DishListItem
val popup = PopupMenu(requireActivity(), view)
popup.apply {
menuInflater.inflate(R.menu.dish_context_menu, menu)
setOnMenuItemClickListener {
when (it.itemId) {
// Open edit dish activity when editing
R.id.edit_dish -> {
thread {
// Get full recipe details
val recipe: Recipe
try {
recipe = getRecipeDetails(requireContext(), item.id)
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
return@thread
}
val intent = Intent(activity, EditDishActivity::class.java)
intent.putExtra("recipe_json", Json.encodeToString(recipe))
launcher.launch(intent)
}
true
}
R.id.remove_dish -> {
AlertDialog.Builder(activity)
.setTitle("Delete dish")
.setMessage("Are you sure you want to delete this dish?")
.setPositiveButton(R.string.delete_label) { _, _ ->
(parent.adapter as DishAdapter).remove(item)
thread {
try {
deleteRecipe(requireContext(), item.id)
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
}
}
}
.setNegativeButton(R.string.cancel_label, null)
.show()
true
}
else -> false
}
}
show()
}
return@setOnItemLongClickListener true
}
binding.addDish.setOnClickListener {
val popup = PopupMenu(requireActivity(), it)
@ -46,13 +124,13 @@ class DishesFragment : Fragment() {
when (it.itemId) {
R.id.create_manually -> {
val intent = Intent(requireContext(), CreateDishActivity::class.java)
createDishLauncher.launch(intent)
launcher.launch(intent)
true
}
R.id.create_with_ai -> {
val intent = Intent(requireContext(), GenerateRecipeActivity::class.java)
createDishLauncher.launch(intent)
launcher.launch(intent)
true
}
@ -98,12 +176,7 @@ class DishesFragment : Fragment() {
binding.dishesList.emptyView = binding.emptyDishesList
val adapter = DishAdapter(requireContext(), dishes) { selectedDish ->
// Open details activity
val intent = Intent(requireContext(), DishDetailsActivity::class.java)
intent.putExtra("dish_id", selectedDish.id)
startActivity(intent)
}
val adapter = DishAdapter(requireContext(), dishes)
binding.dishesList.adapter = adapter
}
}
@ -112,7 +185,7 @@ class DishesFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createDishLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
loadRecipes()
}

View File

@ -1,54 +0,0 @@
package tech.mercantec.easyeat.ui.dishes
import android.os.Bundle
import android.text.Html
import android.text.Html.FROM_HTML_MODE_LEGACY
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import kotlinx.serialization.json.Json
import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.models.Recipe
class RecipeFragment : Fragment() {
private var recipe: Recipe? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { args ->
recipe = args.getString("RECIPE")?.let { Json.decodeFromString<Recipe>(it) }
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
val binding = inflater.inflate(R.layout.fragment_recipe, container, false)
recipe?.let { recipe ->
binding.findViewById<TextView>(R.id.title).text = recipe.name
binding.findViewById<TextView>(R.id.ingredients).text =
Html.fromHtml(
"<ul>" +
recipe.ingredients.map { "<li>${it.amount} ${it.unit} ${it.name}</li>" } +
"</ul>",
FROM_HTML_MODE_LEGACY
)
binding.findViewById<TextView>(R.id.directions).text =
Html.fromHtml(
"<ul>" +
recipe.directions.map { "<li>${it}</li>" } +
"</ul>",
FROM_HTML_MODE_LEGACY
)
}
return binding
}
}

View File

@ -9,15 +9,7 @@
android:layout_height="wrap_content"
android:padding="30sp"
android:orientation="vertical">
<Button
android:id="@+id/editRecipeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:backgroundTint="@color/cyan"
android:text="@string/edit_recipe"
android:tint="@android:color/white"
/>
<TextView
android:id="@+id/dishDetailName"
android:layout_width="match_parent"

View File

@ -29,7 +29,7 @@
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/empty_dishes_list"
/>
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_dish"

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="30dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textSize="24sp"
/>
<TextView
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/ingredients_label"
style="@style/HighContrastText"
/>
<TextView
android:id="@+id/ingredients"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/directions_label"
style="@style/HighContrastText"
/>
<TextView
android:id="@+id/directions"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

View File

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

View File

@ -56,7 +56,6 @@
<string name="instructions_label">Instructions</string>
<string name="increment_multiplier_desc">Increment portion count</string>
<string name="decrement_multiplier_desc">Decrement portion size</string>
<string name="edit_recipe">Edit Recipe</string>
<string name="empty_dishes_list">You have not created any dishes yet</string>
<string name="allergies_label">Allergies</string>
<string name="allergies_hint">Lactose, Gluten</string>