Merge branch 'master' of git.reim.ar:ReiMerc/easyeat

This commit is contained in:
Jeas0001 2025-05-08 11:05:50 +02:00
commit 2a4b0c671d
7 changed files with 111 additions and 24 deletions

View File

@ -94,6 +94,6 @@ inline fun <reified Req, reified Res> requestJson(ctx: Context, method: String,
Log.e("EasyEat", e.message!!) Log.e("EasyEat", e.message!!)
Log.e("EasyEat", response.body) Log.e("EasyEat", response.body)
throw ApiRequestException("Failed to parse response: $response", e) throw ApiRequestException("Failed to parse response: ${response.body}", e)
} }
} }

View File

@ -11,12 +11,16 @@ fun getShoppingList(ctx: Context): Array<ShoppingListItem> {
@Serializable @Serializable
data class AddShoppingItemRequest(val name: String, val amount: Double?, val unit: String?, val checked: Boolean) data class AddShoppingItemRequest(val name: String, val amount: Double?, val unit: String?, val checked: Boolean)
fun addShoppingItem(ctx: Context, name: String, amount: Double?, unit: String?) { fun addShoppingItem(ctx: Context, name: String, amount: Double?, unit: String?): ShoppingListItem {
val request = AddShoppingItemRequest(name, amount, unit, false) val request = AddShoppingItemRequest(name, amount, unit, false)
requestJson<AddShoppingItemRequest, Boolean>(ctx, "POST", "/api/ShoppingList/add", request) return requestJson<AddShoppingItemRequest, ShoppingListItem>(ctx, "POST", "/api/ShoppingList/add", request)
} }
fun toggleShoppingItemChecked(ctx: Context, item: ShoppingListItem) { fun toggleShoppingItemChecked(ctx: Context, item: ShoppingListItem) {
requestJson<Unit, Boolean>(ctx, "PUT", "/api/ShoppingList/check?itemId=${item.id}", null) requestJson<Unit, Boolean>(ctx, "PUT", "/api/ShoppingList/check?itemId=${item.id}", null)
} }
fun deleteShoppingItem(ctx: Context, item: ShoppingListItem) {
requestJson<Unit, Boolean>(ctx, "DELETE", "/api/ShoppingList/delete?itemId=${item.id}", null)
}

View File

@ -1,13 +1,13 @@
package tech.mercantec.easyeat.ui.shopping_list package tech.mercantec.easyeat.ui.shopping_list
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.EditText import android.widget.EditText
import android.widget.PopupMenu
import android.widget.Spinner import android.widget.Spinner
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -15,29 +15,27 @@ import tech.mercantec.easyeat.R
import tech.mercantec.easyeat.databinding.FragmentShoppingListBinding import tech.mercantec.easyeat.databinding.FragmentShoppingListBinding
import tech.mercantec.easyeat.helpers.ApiRequestException import tech.mercantec.easyeat.helpers.ApiRequestException
import tech.mercantec.easyeat.helpers.addShoppingItem import tech.mercantec.easyeat.helpers.addShoppingItem
import tech.mercantec.easyeat.helpers.deleteShoppingItem
import tech.mercantec.easyeat.helpers.getShoppingList import tech.mercantec.easyeat.helpers.getShoppingList
import tech.mercantec.easyeat.helpers.toggleShoppingItemChecked import tech.mercantec.easyeat.helpers.toggleShoppingItemChecked
import tech.mercantec.easyeat.models.Dish
import tech.mercantec.easyeat.models.ShoppingListItem import tech.mercantec.easyeat.models.ShoppingListItem
import java.util.ArrayList import java.util.ArrayList
import kotlin.concurrent.thread import kotlin.concurrent.thread
class ShoppingListFragment : Fragment() { class ShoppingListFragment : Fragment() {
private var _binding: FragmentShoppingListBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
_binding = FragmentShoppingListBinding.inflate(inflater, container, false) val binding = FragmentShoppingListBinding.inflate(inflater, container, false)
val root: View = binding.root
binding.shoppingList.visibility = View.GONE
binding.emptyShoppingList.visibility = View.GONE
binding.addToShoppingList.visibility = View.GONE
binding.loading.visibility = View.VISIBLE
// Fetch shopping list items
thread { thread {
val items: Array<ShoppingListItem> val items: Array<ShoppingListItem>
try { try {
@ -45,16 +43,25 @@ class ShoppingListFragment : Fragment() {
} catch (e: ApiRequestException) { } catch (e: ApiRequestException) {
activity?.runOnUiThread { activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
binding.loading.visibility = View.GONE
} }
return@thread return@thread
} }
activity?.runOnUiThread { activity?.runOnUiThread {
binding.shoppingList.visibility = View.VISIBLE
binding.emptyShoppingList.visibility = View.VISIBLE
binding.addToShoppingList.visibility = View.VISIBLE
binding.loading.visibility = View.GONE
binding.shoppingList.emptyView = binding.emptyShoppingList
binding.shoppingList.adapter = ShoppingItemAdapter(requireContext(), ArrayList(items.toMutableList())) binding.shoppingList.adapter = ShoppingItemAdapter(requireContext(), ArrayList(items.toMutableList()))
} }
} }
// Check / uncheck when clicking shopping item
binding.shoppingList.setOnItemClickListener { parent, view, position, id -> binding.shoppingList.setOnItemClickListener { parent, view, position, id ->
val item = parent.getItemAtPosition(position) as ShoppingListItem val item = parent.getItemAtPosition(position) as ShoppingListItem
item.checked = !item.checked item.checked = !item.checked
@ -73,6 +80,41 @@ class ShoppingListFragment : Fragment() {
adapter.insert(item, position) adapter.insert(item, position)
} }
// Show context menu when long clicking shopping item
binding.shoppingList.setOnItemLongClickListener { parent, view, position, id ->
val item = parent.getItemAtPosition(position) as ShoppingListItem
val popup = PopupMenu(requireActivity(), view)
popup.apply {
menuInflater.inflate(R.menu.shopping_item_context_menu, menu)
setOnMenuItemClickListener {
when (it.itemId) {
R.id.remove_shopping_item -> {
(parent.adapter as ShoppingItemAdapter).remove(item)
thread {
try {
deleteShoppingItem(requireContext(), item)
} catch (e: ApiRequestException) {
activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
}
}
true
}
else -> false
}
}
show()
}
return@setOnItemLongClickListener true
}
// Show new item dialog when clicking add
binding.addToShoppingList.setOnClickListener { binding.addToShoppingList.setOnClickListener {
val view = requireActivity().layoutInflater.inflate(R.layout.dialog_add_to_shopping_list, null) val view = requireActivity().layoutInflater.inflate(R.layout.dialog_add_to_shopping_list, null)
@ -89,17 +131,25 @@ class ShoppingListFragment : Fragment() {
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false
thread { thread {
val item: ShoppingListItem
try { try {
addShoppingItem(requireContext(), name, amount, unit) item = addShoppingItem(requireContext(), name, amount, unit)
} catch (e: ApiRequestException) { } catch (e: ApiRequestException) {
activity?.runOnUiThread { activity?.runOnUiThread {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
} }
return@thread
} finally { } finally {
activity?.runOnUiThread { activity?.runOnUiThread {
dialog.dismiss() dialog.dismiss()
} }
} }
activity?.runOnUiThread {
val adapter = binding.shoppingList.adapter as ShoppingItemAdapter
adapter.insert(item, adapter.count)
}
} }
} }
.setNegativeButton(R.string.cancel_label) { dialog, _ -> .setNegativeButton(R.string.cancel_label) { dialog, _ ->
@ -115,11 +165,6 @@ class ShoppingListFragment : Fragment() {
dialog.findViewById<Spinner>(R.id.unit_selector).adapter = adapter dialog.findViewById<Spinner>(R.id.unit_selector).adapter = adapter
} }
return root return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
} }

View File

@ -0,0 +1,10 @@
<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,10 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"
android:indeterminate="true"
/>
<ListView <ListView
android:id="@+id/shopping_list" android:id="@+id/shopping_list"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -13,15 +22,24 @@
android:dividerHeight="1dp" android:dividerHeight="1dp"
/> />
<TextView
android:id="@+id/empty_shopping_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/empty_shopping_list"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_to_shopping_list" android:id="@+id/add_to_shopping_list"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_margin="16dp" android:layout_margin="16dp"
android:backgroundTint="@color/cyan" android:backgroundTint="@color/cyan"
app:srcCompat="@android:drawable/ic_input_add" app:srcCompat="@android:drawable/ic_input_add"
app:tint="@android:color/white" app:tint="@android:color/white"
/> />
</FrameLayout> </RelativeLayout>

View File

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

View File

@ -39,6 +39,8 @@
<string name="change_password_label">Change password</string> <string name="change_password_label">Change password</string>
<string name="logout_label">Log out</string> <string name="logout_label">Log out</string>
<string name="checked_desc">Checked</string> <string name="checked_desc">Checked</string>
<string name="delete_label">Delete</string>
<string name="empty_shopping_list">Your shopping list is empty</string>
<string-array name="units"> <string-array name="units">
<item></item> <item></item>
<item>g</item> <item>g</item>