Merge branch 'master' of git.reim.ar:ReiMerc/easyeat
This commit is contained in:
commit
80e52cd22b
@ -102,6 +102,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@ package tech.mercantec.easyeat.helpers
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import tech.mercantec.easyeat.models.ShoppingListItem
|
||||||
@Serializable
|
|
||||||
data class ShoppingListItem(val id: Int, val name: String, val amount: Double?, val unit: String?, val checked: Boolean)
|
|
||||||
|
|
||||||
fun getShoppingList(ctx: Context): Array<ShoppingListItem> {
|
fun getShoppingList(ctx: Context): Array<ShoppingListItem> {
|
||||||
return requestJson<Unit, Array<ShoppingListItem>>(ctx, "GET", "/api/ShoppingList/get", null)
|
return requestJson<Unit, Array<ShoppingListItem>>(ctx, "GET", "/api/ShoppingList/get", null)
|
||||||
@ -13,8 +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) {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
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)
|
@ -0,0 +1,60 @@
|
|||||||
|
package tech.mercantec.easyeat.ui.shopping_list
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.icu.text.DecimalFormat
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import tech.mercantec.easyeat.R
|
||||||
|
import tech.mercantec.easyeat.models.ShoppingListItem
|
||||||
|
|
||||||
|
class ShoppingItemAdapter(context: Context, items: ArrayList<ShoppingListItem>)
|
||||||
|
: ArrayAdapter<ShoppingListItem>(context, R.layout.shopping_list_item, items) {
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
val item = getItem(position)
|
||||||
|
val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.shopping_list_item, parent, false)
|
||||||
|
|
||||||
|
item?.let { item ->
|
||||||
|
val checkmarkView = view.findViewById<ImageView>(R.id.checkmark)
|
||||||
|
val amountView = view.findViewById<TextView>(R.id.amount)
|
||||||
|
val unitView = view.findViewById<TextView>(R.id.unit)
|
||||||
|
val nameView = view.findViewById<TextView>(R.id.name)
|
||||||
|
val textViews = listOf(amountView, unitView, nameView)
|
||||||
|
|
||||||
|
amountView.text = DecimalFormat("#.##").format(item.amount)
|
||||||
|
unitView.text = item.unit
|
||||||
|
nameView.text = item.name
|
||||||
|
|
||||||
|
if (item.checked) {
|
||||||
|
textViews.forEach {
|
||||||
|
it.paintFlags = it.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
|
||||||
|
|
||||||
|
val color = TypedValue()
|
||||||
|
context.theme.resolveAttribute(R.attr.colorDisabled, color, true)
|
||||||
|
it.setTextColor(ContextCompat.getColor(context, color.resourceId))
|
||||||
|
|
||||||
|
checkmarkView.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textViews.forEach {
|
||||||
|
it.paintFlags = it.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
|
||||||
|
|
||||||
|
val color = TypedValue()
|
||||||
|
context.theme.resolveAttribute(android.R.attr.textColorSecondary, color, true)
|
||||||
|
it.setTextColor(ContextCompat.getColor(context, color.resourceId))
|
||||||
|
|
||||||
|
checkmarkView.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
}
|
@ -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,24 +15,106 @@ 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.toggleShoppingItemChecked
|
||||||
|
import tech.mercantec.easyeat.models.ShoppingListItem
|
||||||
|
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 {
|
||||||
|
val items: Array<ShoppingListItem>
|
||||||
|
try {
|
||||||
|
items = getShoppingList(requireContext())
|
||||||
|
} catch (e: ApiRequestException) {
|
||||||
|
activity?.runOnUiThread {
|
||||||
|
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
|
binding.loading.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
return@thread
|
||||||
|
}
|
||||||
|
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check / uncheck when clicking shopping item
|
||||||
|
binding.shoppingList.setOnItemClickListener { parent, view, position, id ->
|
||||||
|
val item = parent.getItemAtPosition(position) as ShoppingListItem
|
||||||
|
item.checked = !item.checked
|
||||||
|
|
||||||
|
thread {
|
||||||
|
try {
|
||||||
|
toggleShoppingItemChecked(requireContext(), item)
|
||||||
|
} catch (e: ApiRequestException) {
|
||||||
|
Toast.makeText(requireContext(), e.message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter = parent.adapter as ShoppingItemAdapter
|
||||||
|
|
||||||
|
adapter.remove(item)
|
||||||
|
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)
|
||||||
|
|
||||||
@ -49,17 +131,35 @@ 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
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel_label) { dialog, _ ->
|
.setNegativeButton(R.string.cancel_label) { dialog, _ ->
|
||||||
@ -75,11 +175,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
10
app/app/src/main/res/drawable/ic_check_24px.xml
Normal file
10
app/app/src/main/res/drawable/ic_check_24px.xml
Normal 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="M382,720L154,492L211,435L382,606L749,239L806,296L382,720Z"/>
|
||||||
|
</vector>
|
10
app/app/src/main/res/drawable/ic_delete_24px.xml
Normal file
10
app/app/src/main/res/drawable/ic_delete_24px.xml
Normal 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>
|
@ -2,11 +2,9 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
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"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/container"
|
android:id="@+id/container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:paddingTop="?attr/actionBarSize">
|
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/nav_host_fragment_activity_main"
|
android:id="@+id/nav_host_fragment_activity_main"
|
||||||
|
@ -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>
|
||||||
|
45
app/app/src/main/res/layout/shopping_list_item.xml
Normal file
45
app/app/src/main/res/layout/shopping_list_item.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/checkmark"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@drawable/ic_check_24px"
|
||||||
|
app:tint="@color/green"
|
||||||
|
android:contentDescription="@string/checked_desc"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/amount"
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textAlignment="textEnd"
|
||||||
|
android:textStyle="bold"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/unit"
|
||||||
|
android:layout_marginStart="3dp"
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_marginStart="3dp"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
8
app/app/src/main/res/menu/shopping_item_context_menu.xml
Normal file
8
app/app/src/main/res/menu/shopping_item_context_menu.xml
Normal 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>
|
@ -4,6 +4,7 @@
|
|||||||
<item name="colorPrimaryVariant">@color/dark_cyan</item>
|
<item name="colorPrimaryVariant">@color/dark_cyan</item>
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
<item name="colorSecondary">@color/dark_gray</item>
|
<item name="colorSecondary">@color/dark_gray</item>
|
||||||
|
<item name="colorDisabled">@color/dark_gray</item>
|
||||||
<item name="colorSurface">@color/dark_cyan</item>
|
<item name="colorSurface">@color/dark_cyan</item>
|
||||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||||
<item name="android:colorBackground">@color/black</item>
|
<item name="android:colorBackground">@color/black</item>
|
||||||
|
6
app/app/src/main/res/values/attrs.xml
Normal file
6
app/app/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="EasyEat">
|
||||||
|
<attr name="colorDisabled" format="color" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
@ -7,4 +7,5 @@
|
|||||||
<color name="black">#FF242424</color>
|
<color name="black">#FF242424</color>
|
||||||
<color name="white">#FFF9F9F9</color>
|
<color name="white">#FFF9F9F9</color>
|
||||||
<color name="red">#D62D2D </color>
|
<color name="red">#D62D2D </color>
|
||||||
|
<color name="green">#4CAF50</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -38,6 +38,9 @@
|
|||||||
<string name="ingredient_name_hint">Beef</string>
|
<string name="ingredient_name_hint">Beef</string>
|
||||||
<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="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>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<item name="colorPrimaryVariant">@color/dark_cyan</item>
|
<item name="colorPrimaryVariant">@color/dark_cyan</item>
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
<item name="colorSecondary">@color/dark_gray</item>
|
<item name="colorSecondary">@color/dark_gray</item>
|
||||||
|
<item name="colorDisabled">@color/gray</item>
|
||||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||||
<item name="android:colorBackground">@color/white</item>
|
<item name="android:colorBackground">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -28,63 +28,32 @@ namespace API.BusinessLogic
|
|||||||
{
|
{
|
||||||
var user = await _dbAccess.ReadShoppingList(userId);
|
var user = await _dbAccess.ReadShoppingList(userId);
|
||||||
|
|
||||||
List<ShoppingList> shoppingList = user.ShoppingList;
|
List<ShoppingListItem> shoppingList = user.ShoppingList;
|
||||||
|
|
||||||
if (shoppingList.Any(s => s.Name == listItemDTO.Name))
|
if (shoppingList.Any(s => s.Name == listItemDTO.Name))
|
||||||
{
|
{
|
||||||
ShoppingList item = shoppingList.Where(s => s.Name == listItemDTO.Name).FirstOrDefault();
|
ShoppingListItem item = shoppingList.Where(s => s.Name == listItemDTO.Name).FirstOrDefault();
|
||||||
shoppingList.Remove(item);
|
shoppingList.Remove(item);
|
||||||
|
|
||||||
if (item.Unit == listItemDTO.Unit)
|
user.ShoppingList.Add(await UnitAdjustmentSameName(listItemDTO, item));
|
||||||
{
|
|
||||||
item.Amount += listItemDTO.Amount;
|
|
||||||
}
|
|
||||||
else if (item.Unit == "g" && listItemDTO.Unit == "kg")
|
|
||||||
{
|
|
||||||
item.Amount = (item.Amount / 1000) + listItemDTO.Amount;
|
|
||||||
item.Unit = "kg";
|
|
||||||
}
|
|
||||||
else if (item.Unit == "ml" && listItemDTO.Unit == "l")
|
|
||||||
{
|
|
||||||
item.Amount = (item.Amount / 1000) + listItemDTO.Amount;
|
|
||||||
item.Unit = "l";
|
|
||||||
}
|
|
||||||
else if (item.Unit == "dl" && listItemDTO.Unit == "l")
|
|
||||||
{
|
|
||||||
item.Amount = (item.Amount / 10) + listItemDTO.Amount;
|
|
||||||
item.Unit = "l";
|
|
||||||
}
|
|
||||||
|
|
||||||
item.Checked = false;
|
|
||||||
|
|
||||||
if (item.Unit == "g" && item.Amount >= 1000)
|
|
||||||
{
|
|
||||||
item.Unit = "kg";
|
|
||||||
item.Amount = item.Amount / 1000;
|
|
||||||
}
|
|
||||||
else if (item.Unit == "ml" && item.Amount >= 1000)
|
|
||||||
{
|
|
||||||
item.Unit = "l";
|
|
||||||
item.Amount = item.Amount / 1000;
|
|
||||||
}
|
|
||||||
else if (item.Unit == "dl" && item.Amount >= 10)
|
|
||||||
{
|
|
||||||
item.Unit = "l";
|
|
||||||
item.Amount = item.Amount / 10;
|
|
||||||
}
|
|
||||||
user.ShoppingList.Add(item);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ShoppingList newItem = new ShoppingList();
|
ShoppingListItem newItem = new ShoppingListItem();
|
||||||
newItem.Name = listItemDTO.Name;
|
newItem.Name = listItemDTO.Name;
|
||||||
newItem.Amount = listItemDTO.Amount;
|
newItem.Amount = listItemDTO.Amount;
|
||||||
newItem.Unit = listItemDTO.Unit;
|
newItem.Unit = listItemDTO.Unit;
|
||||||
newItem.Checked = false;
|
newItem.Checked = false;
|
||||||
|
|
||||||
user.ShoppingList.Add(newItem);
|
user.ShoppingList.Add(await UnitAdjustment(newItem));
|
||||||
}
|
}
|
||||||
return await _dbAccess.UpdateShoppingList(user);
|
bool succes = await _dbAccess.AddItemToShoppingList(user);
|
||||||
|
|
||||||
|
var updatedShoppingList = await _dbAccess.ReadShoppingList(userId);
|
||||||
|
|
||||||
|
if (succes) { return new OkObjectResult(updatedShoppingList.ShoppingList.Where(s => s.Name == listItemDTO.Name).First()); }
|
||||||
|
|
||||||
|
return new ConflictObjectResult(new { message = "Could not save to database" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the shoppinglist and tries to find the item and when it does it checks/unchecks that item
|
// Gets the shoppinglist and tries to find the item and when it does it checks/unchecks that item
|
||||||
@ -134,11 +103,11 @@ namespace API.BusinessLogic
|
|||||||
{
|
{
|
||||||
var user = await _dbAccess.ReadShoppingList(userId);
|
var user = await _dbAccess.ReadShoppingList(userId);
|
||||||
|
|
||||||
int itemIndex = user.ShoppingList.FindIndex(x => x.Id == itemId);
|
var item = user.ShoppingList.Where(x => x.Id == itemId).FirstOrDefault();
|
||||||
|
|
||||||
user.ShoppingList.RemoveAt(itemIndex);
|
if (item == null) { return new ConflictObjectResult(new { message = "Could not find item" }); }
|
||||||
|
|
||||||
return await _dbAccess.UpdateShoppingList(user);
|
return await _dbAccess.DeleteItemFromShoppinglist(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds an entire recipes ingredients to the shoppinglist
|
// Adds an entire recipes ingredients to the shoppinglist
|
||||||
@ -150,66 +119,96 @@ namespace API.BusinessLogic
|
|||||||
|
|
||||||
foreach (var ingredient in ingredients)
|
foreach (var ingredient in ingredients)
|
||||||
{
|
{
|
||||||
List<ShoppingList> shoppingList = user.ShoppingList;
|
List<ShoppingListItem> shoppingList = user.ShoppingList;
|
||||||
|
|
||||||
if (shoppingList.Any(s => s.Name == ingredient.Name))
|
if (shoppingList.Any(s => s.Name == ingredient.Name))
|
||||||
{
|
{
|
||||||
ShoppingList item = shoppingList.Where(s => s.Name == ingredient.Name).FirstOrDefault();
|
ShoppingListItem item = shoppingList.Where(s => s.Name == ingredient.Name).FirstOrDefault();
|
||||||
shoppingList.Remove(item);
|
shoppingList.Remove(item);
|
||||||
|
|
||||||
if (item.Unit == ingredient.Unit)
|
ShoppingListItemDTO listItemDTO = new ShoppingListItemDTO();
|
||||||
{
|
listItemDTO.Name = ingredient.Name;
|
||||||
item.Amount += ingredient.Amount;
|
listItemDTO.Amount = ingredient.Amount;
|
||||||
}
|
listItemDTO.Unit = ingredient.Unit;
|
||||||
else if (item.Unit == "g" && ingredient.Unit == "kg")
|
listItemDTO.Checked = false;
|
||||||
{
|
|
||||||
item.Amount = (item.Amount / 1000) + ingredient.Amount;
|
|
||||||
item.Unit = "kg";
|
|
||||||
}
|
|
||||||
else if (item.Unit == "ml" && item.Unit == "l")
|
|
||||||
{
|
|
||||||
item.Amount = (item.Amount / 1000) + ingredient.Amount;
|
|
||||||
item.Unit = "l";
|
|
||||||
}
|
|
||||||
else if (item.Unit == "dl" && item.Unit == "l")
|
|
||||||
{
|
|
||||||
item.Amount = (item.Amount / 10) + ingredient.Amount;
|
|
||||||
item.Unit = "l";
|
|
||||||
}
|
|
||||||
|
|
||||||
item.Checked = false;
|
user.ShoppingList.Add(await UnitAdjustmentSameName(listItemDTO, item));
|
||||||
|
|
||||||
if (item.Unit == "g" && item.Amount >= 1000)
|
|
||||||
{
|
|
||||||
item.Unit = "kg";
|
|
||||||
item.Amount = item.Amount / 1000;
|
|
||||||
}
|
|
||||||
else if (item.Unit == "ml" && item.Amount >= 1000)
|
|
||||||
{
|
|
||||||
item.Unit = "l";
|
|
||||||
item.Amount = item.Amount / 1000;
|
|
||||||
}
|
|
||||||
else if (item.Unit == "dl" && item.Amount >= 10)
|
|
||||||
{
|
|
||||||
item.Unit = "l";
|
|
||||||
item.Amount = item.Amount / 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.ShoppingList.Add(item);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ShoppingList newItem = new ShoppingList();
|
ShoppingListItem newItem = new ShoppingListItem();
|
||||||
newItem.Name = ingredient.Name;
|
newItem.Name = ingredient.Name;
|
||||||
newItem.Amount = ingredient.Amount;
|
newItem.Amount = ingredient.Amount;
|
||||||
newItem.Unit = ingredient.Unit;
|
newItem.Unit = ingredient.Unit;
|
||||||
newItem.Checked = false;
|
newItem.Checked = false;
|
||||||
|
|
||||||
user.ShoppingList.Add(newItem);
|
user.ShoppingList.Add(await UnitAdjustment(newItem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await _dbAccess.UpdateShoppingList(user);
|
return await _dbAccess.UpdateShoppingList(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ShoppingListItem> UnitAdjustmentSameName(ShoppingListItemDTO listItemDTO, ShoppingListItem listItem)
|
||||||
|
{
|
||||||
|
if (listItem.Unit == listItemDTO.Unit)
|
||||||
|
{
|
||||||
|
listItem.Amount += listItemDTO.Amount;
|
||||||
|
}
|
||||||
|
else if (listItem.Unit == "g" && listItemDTO.Unit == "kg")
|
||||||
|
{
|
||||||
|
listItem.Amount = (listItem.Amount / 1000) + listItemDTO.Amount;
|
||||||
|
listItem.Unit = "kg";
|
||||||
|
}
|
||||||
|
else if (listItem.Unit == "kg" && listItemDTO.Unit == "g")
|
||||||
|
{
|
||||||
|
listItem.Amount = (listItemDTO.Amount / 1000) + listItem.Amount;
|
||||||
|
listItem.Unit = "kg";
|
||||||
|
}
|
||||||
|
else if (listItem.Unit == "ml" && listItemDTO.Unit == "l")
|
||||||
|
{
|
||||||
|
listItem.Amount = (listItem.Amount / 1000) + listItemDTO.Amount;
|
||||||
|
listItem.Unit = "l";
|
||||||
|
}
|
||||||
|
else if (listItem.Unit == "l" && listItemDTO.Unit == "ml")
|
||||||
|
{
|
||||||
|
listItem.Amount = (listItemDTO.Amount / 1000) + listItem.Amount;
|
||||||
|
listItem.Unit = "l";
|
||||||
|
}
|
||||||
|
else if (listItem.Unit == "dl" && listItemDTO.Unit == "l")
|
||||||
|
{
|
||||||
|
listItem.Amount = (listItem.Amount / 10) + listItemDTO.Amount;
|
||||||
|
listItem.Unit = "l";
|
||||||
|
}
|
||||||
|
else if (listItem.Unit == "l" && listItemDTO.Unit == "dl")
|
||||||
|
{
|
||||||
|
listItem.Amount = (listItemDTO.Amount / 10) + listItem.Amount;
|
||||||
|
listItem.Unit = "l";
|
||||||
|
}
|
||||||
|
|
||||||
|
listItem.Checked = false;
|
||||||
|
|
||||||
|
return await UnitAdjustment(listItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ShoppingListItem> UnitAdjustment(ShoppingListItem listItem)
|
||||||
|
{
|
||||||
|
if (listItem.Unit == "g" && listItem.Amount >= 1000)
|
||||||
|
{
|
||||||
|
listItem.Unit = "kg";
|
||||||
|
listItem.Amount = listItem.Amount / 1000;
|
||||||
|
}
|
||||||
|
else if (listItem.Unit == "ml" && listItem.Amount >= 1000)
|
||||||
|
{
|
||||||
|
listItem.Unit = "l";
|
||||||
|
listItem.Amount = listItem.Amount / 1000;
|
||||||
|
}
|
||||||
|
else if (listItem.Unit == "dl" && listItem.Amount >= 10)
|
||||||
|
{
|
||||||
|
listItem.Unit = "l";
|
||||||
|
listItem.Amount = listItem.Amount / 10;
|
||||||
|
}
|
||||||
|
return listItem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ namespace API.BusinessLogic
|
|||||||
Password = hashedPassword,
|
Password = hashedPassword,
|
||||||
Salt = salt,
|
Salt = salt,
|
||||||
Recipes = new List<Recipe>(),
|
Recipes = new List<Recipe>(),
|
||||||
ShoppingList = new List<ShoppingList>(),
|
ShoppingList = new List<ShoppingListItem>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return await _dbAccess.CreateUser(user);
|
return await _dbAccess.CreateUser(user);
|
||||||
|
@ -11,7 +11,7 @@ namespace API.DBAccess
|
|||||||
|
|
||||||
public DbSet<Recipe> Recipes { get; set; }
|
public DbSet<Recipe> Recipes { get; set; }
|
||||||
|
|
||||||
public DbSet<ShoppingList> ShoppingList { get; set; }
|
public DbSet<ShoppingListItem> ShoppingList { get; set; }
|
||||||
|
|
||||||
public DBContext(DbContextOptions<DBContext> options) : base(options) { }
|
public DBContext(DbContextOptions<DBContext> options) : base(options) { }
|
||||||
}
|
}
|
||||||
|
@ -34,5 +34,27 @@ namespace API.DBAccess
|
|||||||
|
|
||||||
return new ConflictObjectResult(new { message = "Could not save to database" });
|
return new ConflictObjectResult(new { message = "Could not save to database" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds an item to the shoppinglist
|
||||||
|
public async Task<bool> AddItemToShoppingList(User user)
|
||||||
|
{
|
||||||
|
_context.Entry(user).State = EntityState.Modified;
|
||||||
|
|
||||||
|
bool saved = await _context.SaveChangesAsync() >= 1;
|
||||||
|
|
||||||
|
if (saved) { return true; }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> DeleteItemFromShoppinglist(ShoppingListItem item)
|
||||||
|
{
|
||||||
|
_context.ShoppingList.Remove(item);
|
||||||
|
bool saved = await _context.SaveChangesAsync() >= 0;
|
||||||
|
|
||||||
|
if (saved) { return new OkObjectResult(saved); }
|
||||||
|
|
||||||
|
return new ConflictObjectResult(new { message = "Could not save to database" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
namespace API.Models.ShoppingListModels
|
namespace API.Models.ShoppingListModels
|
||||||
{
|
{
|
||||||
public class ShoppingList
|
public class ShoppingListItem
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -21,6 +21,6 @@ namespace API.Models.UserModels
|
|||||||
|
|
||||||
public List<Recipe> Recipes { get; set; }
|
public List<Recipe> Recipes { get; set; }
|
||||||
|
|
||||||
public List<ShoppingList> ShoppingList { get; set; }
|
public List<ShoppingListItem> ShoppingList { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user