From f924d2083b5970c48a42e2ffbc1fcb13b7789f95 Mon Sep 17 00:00:00 2001 From: Reimar Date: Thu, 8 May 2025 09:16:12 +0200 Subject: [PATCH 01/10] Show shopping list --- .../easyeat/helpers/shopping_list.kt | 4 +-- .../easyeat/models/ShoppingListItem.kt | 6 ++++ .../ui/shopping_list/ShoppingItemAdapter.kt | 28 +++++++++++++++ .../ui/shopping_list/ShoppingListFragment.kt | 19 ++++++++++ app/app/src/main/res/layout/activity_main.xml | 4 +-- .../main/res/layout/shopping_list_item.xml | 35 +++++++++++++++++++ 6 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 app/app/src/main/java/tech/mercantec/easyeat/models/ShoppingListItem.kt create mode 100644 app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingItemAdapter.kt create mode 100644 app/app/src/main/res/layout/shopping_list_item.xml diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt index 740f9fb..1d5684b 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt @@ -2,9 +2,7 @@ package tech.mercantec.easyeat.helpers import android.content.Context import kotlinx.serialization.Serializable - -@Serializable -data class ShoppingListItem(val id: Int, val name: String, val amount: Double?, val unit: String?, val checked: Boolean) +import tech.mercantec.easyeat.models.ShoppingListItem fun getShoppingList(ctx: Context): Array { return requestJson>(ctx, "GET", "/api/ShoppingList/get", null) diff --git a/app/app/src/main/java/tech/mercantec/easyeat/models/ShoppingListItem.kt b/app/app/src/main/java/tech/mercantec/easyeat/models/ShoppingListItem.kt new file mode 100644 index 0000000..1710ca0 --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/models/ShoppingListItem.kt @@ -0,0 +1,6 @@ +package tech.mercantec.easyeat.models + +import kotlinx.serialization.Serializable + +@Serializable +data class ShoppingListItem(val id: Int, val name: String, val amount: Double?, val unit: String?, val checked: Boolean) diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingItemAdapter.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingItemAdapter.kt new file mode 100644 index 0000000..7458594 --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingItemAdapter.kt @@ -0,0 +1,28 @@ +package tech.mercantec.easyeat.ui.shopping_list + +import android.content.Context +import android.icu.text.DecimalFormat +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.TextView +import tech.mercantec.easyeat.R +import tech.mercantec.easyeat.models.ShoppingListItem + +class ShoppingItemAdapter(context: Context, items: Array) + : ArrayAdapter(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 -> + view.findViewById(R.id.amount).text = DecimalFormat("#.##").format(item.amount) + view.findViewById(R.id.unit).text = item.unit + view.findViewById(R.id.name).text = item.name + } + + return view + } +} diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt index 878cab7..fc074be 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt @@ -15,6 +15,8 @@ import tech.mercantec.easyeat.R import tech.mercantec.easyeat.databinding.FragmentShoppingListBinding import tech.mercantec.easyeat.helpers.ApiRequestException import tech.mercantec.easyeat.helpers.addShoppingItem +import tech.mercantec.easyeat.helpers.getShoppingList +import tech.mercantec.easyeat.models.ShoppingListItem import kotlin.concurrent.thread class ShoppingListFragment : Fragment() { @@ -33,6 +35,23 @@ class ShoppingListFragment : Fragment() { _binding = FragmentShoppingListBinding.inflate(inflater, container, false) val root: View = binding.root + thread { + val items: Array + try { + items = getShoppingList(requireContext()) + } catch (e: ApiRequestException) { + activity?.runOnUiThread { + Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() + } + + return@thread + } + + activity?.runOnUiThread { + binding.shoppingList.adapter = ShoppingItemAdapter(requireContext(), items) + } + } + binding.addToShoppingList.setOnClickListener { val view = requireActivity().layoutInflater.inflate(R.layout.dialog_add_to_shopping_list, null) diff --git a/app/app/src/main/res/layout/activity_main.xml b/app/app/src/main/res/layout/activity_main.xml index 37665d9..e338c8e 100644 --- a/app/app/src/main/res/layout/activity_main.xml +++ b/app/app/src/main/res/layout/activity_main.xml @@ -2,11 +2,9 @@ + android:layout_height="match_parent"> + + + + + + + + + From b3b4562f21fbf6ca497f027661bfe4c511ec904a Mon Sep 17 00:00:00 2001 From: Reimar Date: Thu, 8 May 2025 10:04:29 +0200 Subject: [PATCH 02/10] Allow checking/unchecking shopping items --- .../easyeat/helpers/shopping_list.kt | 4 ++ .../easyeat/models/ShoppingListItem.kt | 2 +- .../ui/shopping_list/ShoppingItemAdapter.kt | 40 +++++++++++++++++-- .../ui/shopping_list/ShoppingListFragment.kt | 23 ++++++++++- .../src/main/res/drawable/ic_check_24px.xml | 10 +++++ .../main/res/layout/shopping_list_item.xml | 10 +++++ app/app/src/main/res/values-night/themes.xml | 1 + app/app/src/main/res/values/attrs.xml | 6 +++ app/app/src/main/res/values/colors.xml | 1 + app/app/src/main/res/values/strings.xml | 1 + app/app/src/main/res/values/themes.xml | 1 + 11 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 app/app/src/main/res/drawable/ic_check_24px.xml create mode 100644 app/app/src/main/res/values/attrs.xml diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt index 1d5684b..aa42d3e 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt @@ -16,3 +16,7 @@ fun addShoppingItem(ctx: Context, name: String, amount: Double?, unit: String?) requestJson(ctx, "POST", "/api/ShoppingList/add", request) } + +fun toggleShoppingItemChecked(ctx: Context, item: ShoppingListItem) { + requestJson(ctx, "PUT", "/api/ShoppingList/check?itemId=${item.id}", null) +} diff --git a/app/app/src/main/java/tech/mercantec/easyeat/models/ShoppingListItem.kt b/app/app/src/main/java/tech/mercantec/easyeat/models/ShoppingListItem.kt index 1710ca0..a97a6a7 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/models/ShoppingListItem.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/models/ShoppingListItem.kt @@ -3,4 +3,4 @@ package tech.mercantec.easyeat.models import kotlinx.serialization.Serializable @Serializable -data class ShoppingListItem(val id: Int, val name: String, val amount: Double?, val unit: String?, val checked: Boolean) +data class ShoppingListItem(val id: Int, var name: String, var amount: Double?, var unit: String?, var checked: Boolean) diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingItemAdapter.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingItemAdapter.kt index 7458594..be952f2 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingItemAdapter.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingItemAdapter.kt @@ -1,16 +1,20 @@ 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: Array) +class ShoppingItemAdapter(context: Context, items: ArrayList) : ArrayAdapter(context, R.layout.shopping_list_item, items) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { @@ -18,9 +22,37 @@ class ShoppingItemAdapter(context: Context, items: Array) val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.shopping_list_item, parent, false) item?.let { item -> - view.findViewById(R.id.amount).text = DecimalFormat("#.##").format(item.amount) - view.findViewById(R.id.unit).text = item.unit - view.findViewById(R.id.name).text = item.name + val checkmarkView = view.findViewById(R.id.checkmark) + val amountView = view.findViewById(R.id.amount) + val unitView = view.findViewById(R.id.unit) + val nameView = view.findViewById(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 diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt index fc074be..b1e9dc3 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt @@ -16,7 +16,10 @@ import tech.mercantec.easyeat.databinding.FragmentShoppingListBinding import tech.mercantec.easyeat.helpers.ApiRequestException import tech.mercantec.easyeat.helpers.addShoppingItem import tech.mercantec.easyeat.helpers.getShoppingList +import tech.mercantec.easyeat.helpers.toggleShoppingItemChecked +import tech.mercantec.easyeat.models.Dish import tech.mercantec.easyeat.models.ShoppingListItem +import java.util.ArrayList import kotlin.concurrent.thread class ShoppingListFragment : Fragment() { @@ -48,10 +51,28 @@ class ShoppingListFragment : Fragment() { } activity?.runOnUiThread { - binding.shoppingList.adapter = ShoppingItemAdapter(requireContext(), items) + binding.shoppingList.adapter = ShoppingItemAdapter(requireContext(), ArrayList(items.toMutableList())) } } + 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) + } + binding.addToShoppingList.setOnClickListener { val view = requireActivity().layoutInflater.inflate(R.layout.dialog_add_to_shopping_list, null) diff --git a/app/app/src/main/res/drawable/ic_check_24px.xml b/app/app/src/main/res/drawable/ic_check_24px.xml new file mode 100644 index 0000000..280f0bd --- /dev/null +++ b/app/app/src/main/res/drawable/ic_check_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/app/src/main/res/layout/shopping_list_item.xml b/app/app/src/main/res/layout/shopping_list_item.xml index 69280e3..f71e2d3 100644 --- a/app/app/src/main/res/layout/shopping_list_item.xml +++ b/app/app/src/main/res/layout/shopping_list_item.xml @@ -1,11 +1,21 @@ + + @color/dark_cyan @color/white @color/dark_gray + @color/dark_gray @color/dark_cyan ?attr/colorPrimaryVariant @color/black diff --git a/app/app/src/main/res/values/attrs.xml b/app/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..d82cf71 --- /dev/null +++ b/app/app/src/main/res/values/attrs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/app/src/main/res/values/colors.xml b/app/app/src/main/res/values/colors.xml index 9c94335..4af2f08 100644 --- a/app/app/src/main/res/values/colors.xml +++ b/app/app/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #FF242424 #FFF9F9F9 #D62D2D + #4CAF50 diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index 3c50a80..741c1d1 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -38,6 +38,7 @@ Beef Change password Log out + Checked g diff --git a/app/app/src/main/res/values/themes.xml b/app/app/src/main/res/values/themes.xml index 30afe6a..eff4e5d 100644 --- a/app/app/src/main/res/values/themes.xml +++ b/app/app/src/main/res/values/themes.xml @@ -4,6 +4,7 @@ @color/dark_cyan @color/white @color/dark_gray + @color/gray ?attr/colorPrimaryVariant @color/white From dfb28385652af5ac395059449a5032d1ace0ba6c Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Thu, 8 May 2025 10:22:43 +0200 Subject: [PATCH 03/10] Returns the added item in the shoppinglist --- backend/API/BusinessLogic/ShoppingListLogic.cs | 8 +++++++- backend/API/DBAccess/ShoppingListDBAccess.cs | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/backend/API/BusinessLogic/ShoppingListLogic.cs b/backend/API/BusinessLogic/ShoppingListLogic.cs index 1651896..d4ae617 100644 --- a/backend/API/BusinessLogic/ShoppingListLogic.cs +++ b/backend/API/BusinessLogic/ShoppingListLogic.cs @@ -84,7 +84,13 @@ namespace API.BusinessLogic user.ShoppingList.Add(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)); } + + 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 diff --git a/backend/API/DBAccess/ShoppingListDBAccess.cs b/backend/API/DBAccess/ShoppingListDBAccess.cs index a9e7c68..d45b43a 100644 --- a/backend/API/DBAccess/ShoppingListDBAccess.cs +++ b/backend/API/DBAccess/ShoppingListDBAccess.cs @@ -34,5 +34,17 @@ namespace API.DBAccess return new ConflictObjectResult(new { message = "Could not save to database" }); } + + // Adds an item to the shoppinglist + public async Task AddItemToShoppingList(User user) + { + _context.Entry(user).State = EntityState.Modified; + + bool saved = await _context.SaveChangesAsync() >= 1; + + if (saved) { return true; } + + return false; + } } } From 7e1583f4853ad35421680fc0a3404ea722f8160d Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Thu, 8 May 2025 10:44:34 +0200 Subject: [PATCH 04/10] Changed delete item in shopping list to work and add item to only return an item and not an array with one item --- .../API/BusinessLogic/ShoppingListLogic.cs | 20 +++++++++---------- backend/API/BusinessLogic/UserLogic.cs | 2 +- backend/API/DBAccess/DBContext.cs | 2 +- backend/API/DBAccess/ShoppingListDBAccess.cs | 10 ++++++++++ .../{ShoppingList.cs => ShoppingListItem.cs} | 2 +- backend/API/Models/UserModels/User.cs | 2 +- 6 files changed, 24 insertions(+), 14 deletions(-) rename backend/API/Models/ShoppingListModels/{ShoppingList.cs => ShoppingListItem.cs} (88%) diff --git a/backend/API/BusinessLogic/ShoppingListLogic.cs b/backend/API/BusinessLogic/ShoppingListLogic.cs index d4ae617..6386134 100644 --- a/backend/API/BusinessLogic/ShoppingListLogic.cs +++ b/backend/API/BusinessLogic/ShoppingListLogic.cs @@ -28,11 +28,11 @@ namespace API.BusinessLogic { var user = await _dbAccess.ReadShoppingList(userId); - List shoppingList = user.ShoppingList; + List shoppingList = user.ShoppingList; 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); if (item.Unit == listItemDTO.Unit) @@ -76,7 +76,7 @@ namespace API.BusinessLogic } else { - ShoppingList newItem = new ShoppingList(); + ShoppingListItem newItem = new ShoppingListItem(); newItem.Name = listItemDTO.Name; newItem.Amount = listItemDTO.Amount; newItem.Unit = listItemDTO.Unit; @@ -88,7 +88,7 @@ namespace API.BusinessLogic var updatedShoppingList = await _dbAccess.ReadShoppingList(userId); - if (succes) { return new OkObjectResult(updatedShoppingList.ShoppingList.Where(s => s.Name == listItemDTO.Name)); } + if (succes) { return new OkObjectResult(updatedShoppingList.ShoppingList.Where(s => s.Name == listItemDTO.Name).First()); } return new ConflictObjectResult(new { message = "Could not save to database" }); } @@ -140,11 +140,11 @@ namespace API.BusinessLogic { 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 @@ -156,11 +156,11 @@ namespace API.BusinessLogic foreach (var ingredient in ingredients) { - List shoppingList = user.ShoppingList; + List shoppingList = user.ShoppingList; 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); if (item.Unit == ingredient.Unit) @@ -205,7 +205,7 @@ namespace API.BusinessLogic } else { - ShoppingList newItem = new ShoppingList(); + ShoppingListItem newItem = new ShoppingListItem(); newItem.Name = ingredient.Name; newItem.Amount = ingredient.Amount; newItem.Unit = ingredient.Unit; diff --git a/backend/API/BusinessLogic/UserLogic.cs b/backend/API/BusinessLogic/UserLogic.cs index f32bd2e..1cbf349 100644 --- a/backend/API/BusinessLogic/UserLogic.cs +++ b/backend/API/BusinessLogic/UserLogic.cs @@ -67,7 +67,7 @@ namespace API.BusinessLogic Password = hashedPassword, Salt = salt, Recipes = new List(), - ShoppingList = new List(), + ShoppingList = new List(), }; return await _dbAccess.CreateUser(user); diff --git a/backend/API/DBAccess/DBContext.cs b/backend/API/DBAccess/DBContext.cs index b18f88e..058193a 100644 --- a/backend/API/DBAccess/DBContext.cs +++ b/backend/API/DBAccess/DBContext.cs @@ -11,7 +11,7 @@ namespace API.DBAccess public DbSet Recipes { get; set; } - public DbSet ShoppingList { get; set; } + public DbSet ShoppingList { get; set; } public DBContext(DbContextOptions options) : base(options) { } } diff --git a/backend/API/DBAccess/ShoppingListDBAccess.cs b/backend/API/DBAccess/ShoppingListDBAccess.cs index d45b43a..dd8b50a 100644 --- a/backend/API/DBAccess/ShoppingListDBAccess.cs +++ b/backend/API/DBAccess/ShoppingListDBAccess.cs @@ -46,5 +46,15 @@ namespace API.DBAccess return false; } + + public async Task 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" }); + } } } diff --git a/backend/API/Models/ShoppingListModels/ShoppingList.cs b/backend/API/Models/ShoppingListModels/ShoppingListItem.cs similarity index 88% rename from backend/API/Models/ShoppingListModels/ShoppingList.cs rename to backend/API/Models/ShoppingListModels/ShoppingListItem.cs index 648e9dc..badee1d 100644 --- a/backend/API/Models/ShoppingListModels/ShoppingList.cs +++ b/backend/API/Models/ShoppingListModels/ShoppingListItem.cs @@ -1,6 +1,6 @@ namespace API.Models.ShoppingListModels { - public class ShoppingList + public class ShoppingListItem { public int Id { get; set; } diff --git a/backend/API/Models/UserModels/User.cs b/backend/API/Models/UserModels/User.cs index 3b13e51..dd196d5 100644 --- a/backend/API/Models/UserModels/User.cs +++ b/backend/API/Models/UserModels/User.cs @@ -21,6 +21,6 @@ namespace API.Models.UserModels public List Recipes { get; set; } - public List ShoppingList { get; set; } + public List ShoppingList { get; set; } } } From 39bcdd9a9bc93d71b60f23248370b1f2632fede7 Mon Sep 17 00:00:00 2001 From: Reimar Date: Thu, 8 May 2025 10:33:28 +0200 Subject: [PATCH 05/10] Implement deleting shopping items --- .../easyeat/helpers/shopping_list.kt | 4 ++ .../ui/shopping_list/ShoppingListFragment.kt | 39 +++++++++++++++++++ .../src/main/res/drawable/ic_delete_24px.xml | 10 +++++ .../res/menu/shopping_item_context_menu.xml | 8 ++++ app/app/src/main/res/values/strings.xml | 1 + 5 files changed, 62 insertions(+) create mode 100644 app/app/src/main/res/drawable/ic_delete_24px.xml create mode 100644 app/app/src/main/res/menu/shopping_item_context_menu.xml diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt index aa42d3e..73ff1bb 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt @@ -20,3 +20,7 @@ fun addShoppingItem(ctx: Context, name: String, amount: Double?, unit: String?) fun toggleShoppingItemChecked(ctx: Context, item: ShoppingListItem) { requestJson(ctx, "PUT", "/api/ShoppingList/check?itemId=${item.id}", null) } + +fun deleteShoppingItem(ctx: Context, item: ShoppingListItem) { + requestJson(ctx, "DELETE", "/api/ShoppingList/delete?itemId=${item.id}", null) +} diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt index b1e9dc3..fee97e3 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt @@ -8,6 +8,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.EditText +import android.widget.PopupMenu import android.widget.Spinner import android.widget.Toast import androidx.fragment.app.Fragment @@ -15,6 +16,7 @@ import tech.mercantec.easyeat.R 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.getShoppingList import tech.mercantec.easyeat.helpers.toggleShoppingItemChecked import tech.mercantec.easyeat.models.Dish @@ -38,6 +40,7 @@ class ShoppingListFragment : Fragment() { _binding = FragmentShoppingListBinding.inflate(inflater, container, false) val root: View = binding.root + // Fetch shopping list items thread { val items: Array try { @@ -55,6 +58,7 @@ class ShoppingListFragment : Fragment() { } } + // Check / uncheck when clicking shopping item binding.shoppingList.setOnItemClickListener { parent, view, position, id -> val item = parent.getItemAtPosition(position) as ShoppingListItem item.checked = !item.checked @@ -73,6 +77,41 @@ class ShoppingListFragment : Fragment() { 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 { val view = requireActivity().layoutInflater.inflate(R.layout.dialog_add_to_shopping_list, null) diff --git a/app/app/src/main/res/drawable/ic_delete_24px.xml b/app/app/src/main/res/drawable/ic_delete_24px.xml new file mode 100644 index 0000000..d1ed443 --- /dev/null +++ b/app/app/src/main/res/drawable/ic_delete_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/app/src/main/res/menu/shopping_item_context_menu.xml b/app/app/src/main/res/menu/shopping_item_context_menu.xml new file mode 100644 index 0000000..48be440 --- /dev/null +++ b/app/app/src/main/res/menu/shopping_item_context_menu.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index 741c1d1..e4bf2ef 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ Change password Log out Checked + Delete g From fa877b2c5e4be797f37788f1d12c66589904a5c5 Mon Sep 17 00:00:00 2001 From: Reimar Date: Thu, 8 May 2025 10:47:10 +0200 Subject: [PATCH 06/10] Show shopping item immediately as it is added to the list --- .../main/java/tech/mercantec/easyeat/helpers/api.kt | 2 +- .../tech/mercantec/easyeat/helpers/shopping_list.kt | 4 ++-- .../easyeat/ui/shopping_list/ShoppingListFragment.kt | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt index c3297eb..28dfc6f 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt @@ -94,6 +94,6 @@ inline fun requestJson(ctx: Context, method: String, Log.e("EasyEat", e.message!!) Log.e("EasyEat", response.body) - throw ApiRequestException("Failed to parse response: $response", e) + throw ApiRequestException("Failed to parse response: ${response.body}", e) } } diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt index 73ff1bb..a9adbc8 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/shopping_list.kt @@ -11,10 +11,10 @@ fun getShoppingList(ctx: Context): Array { @Serializable 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) - requestJson(ctx, "POST", "/api/ShoppingList/add", request) + return requestJson(ctx, "POST", "/api/ShoppingList/add", request) } fun toggleShoppingItemChecked(ctx: Context, item: ShoppingListItem) { diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt index fee97e3..ba837e6 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt @@ -128,17 +128,25 @@ class ShoppingListFragment : Fragment() { dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false thread { + val item: ShoppingListItem try { - addShoppingItem(requireContext(), name, amount, unit) + 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() } } + + activity?.runOnUiThread { + val adapter = binding.shoppingList.adapter as ShoppingItemAdapter + adapter.insert(item, adapter.count) + } } } .setNegativeButton(R.string.cancel_label) { dialog, _ -> From df0c9511000506a66385d5dbb4a57b9c0b7ffc1d Mon Sep 17 00:00:00 2001 From: Reimar Date: Thu, 8 May 2025 11:02:04 +0200 Subject: [PATCH 07/10] Add progress bar and empty message to shopping list --- .../ui/shopping_list/ShoppingListFragment.kt | 32 +++++++++---------- .../res/layout/fragment_shopping_list.xml | 24 ++++++++++++-- app/app/src/main/res/values/strings.xml | 1 + 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt index ba837e6..7fd4d67 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt @@ -1,7 +1,6 @@ package tech.mercantec.easyeat.ui.shopping_list import android.app.AlertDialog -import android.app.Dialog import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -19,26 +18,22 @@ 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.Dish import tech.mercantec.easyeat.models.ShoppingListItem import java.util.ArrayList import kotlin.concurrent.thread 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( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - _binding = FragmentShoppingListBinding.inflate(inflater, container, false) - val root: View = binding.root + val binding = FragmentShoppingListBinding.inflate(inflater, container, false) + + 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 { @@ -48,12 +43,20 @@ class ShoppingListFragment : Fragment() { } 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())) } } @@ -162,11 +165,6 @@ class ShoppingListFragment : Fragment() { dialog.findViewById(R.id.unit_selector).adapter = adapter } - return root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null + return binding.root } } \ No newline at end of file diff --git a/app/app/src/main/res/layout/fragment_shopping_list.xml b/app/app/src/main/res/layout/fragment_shopping_list.xml index 17466ab..dab1872 100644 --- a/app/app/src/main/res/layout/fragment_shopping_list.xml +++ b/app/app/src/main/res/layout/fragment_shopping_list.xml @@ -1,10 +1,19 @@ - + + + + - + diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index e4bf2ef..3ea29e3 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -40,6 +40,7 @@ Log out Checked Delete + Your shopping list is empty g From fd11c89ceac554e6894d77ba45e2d914859bba1c Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Thu, 8 May 2025 11:05:40 +0200 Subject: [PATCH 08/10] Extrated unit adjustment --- .../API/BusinessLogic/ShoppingListLogic.cs | 136 ++++++++---------- 1 file changed, 57 insertions(+), 79 deletions(-) diff --git a/backend/API/BusinessLogic/ShoppingListLogic.cs b/backend/API/BusinessLogic/ShoppingListLogic.cs index 6386134..129a6d7 100644 --- a/backend/API/BusinessLogic/ShoppingListLogic.cs +++ b/backend/API/BusinessLogic/ShoppingListLogic.cs @@ -34,45 +34,8 @@ namespace API.BusinessLogic { ShoppingListItem item = shoppingList.Where(s => s.Name == listItemDTO.Name).FirstOrDefault(); shoppingList.Remove(item); - - if (item.Unit == listItemDTO.Unit) - { - 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); + + user.ShoppingList.Add(await UnitAdjustmentSameName(listItemDTO, item)); } else { @@ -82,7 +45,7 @@ namespace API.BusinessLogic newItem.Unit = listItemDTO.Unit; newItem.Checked = false; - user.ShoppingList.Add(newItem); + user.ShoppingList.Add(await UnitAdjustment(newItem)); } bool succes = await _dbAccess.AddItemToShoppingList(user); @@ -163,45 +126,13 @@ namespace API.BusinessLogic ShoppingListItem item = shoppingList.Where(s => s.Name == ingredient.Name).FirstOrDefault(); shoppingList.Remove(item); - if (item.Unit == ingredient.Unit) - { - item.Amount += ingredient.Amount; - } - else if (item.Unit == "g" && ingredient.Unit == "kg") - { - 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"; - } + ShoppingListItemDTO listItemDTO = new ShoppingListItemDTO(); + listItemDTO.Name = ingredient.Name; + listItemDTO.Amount = ingredient.Amount; + listItemDTO.Unit = ingredient.Unit; + listItemDTO.Checked = false; - 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); + user.ShoppingList.Add(await UnitAdjustmentSameName(listItemDTO, item)); } else { @@ -211,11 +142,58 @@ namespace API.BusinessLogic newItem.Unit = ingredient.Unit; newItem.Checked = false; - user.ShoppingList.Add(newItem); + user.ShoppingList.Add(await UnitAdjustment(newItem)); } } return await _dbAccess.UpdateShoppingList(user); } + + public async Task 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 == "ml" && listItemDTO.Unit == "l") + { + listItem.Amount = (listItem.Amount / 1000) + listItemDTO.Amount; + listItem.Unit = "l"; + } + else if (listItem.Unit == "dl" && listItemDTO.Unit == "l") + { + listItem.Amount = (listItem.Amount / 10) + listItemDTO.Amount; + listItem.Unit = "l"; + } + + listItem.Checked = false; + + return await UnitAdjustment(listItem); + } + + public async Task 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; + } } } From 1b4f9059f4316288ee82a7fc409df8bf58f01204 Mon Sep 17 00:00:00 2001 From: Reimar Date: Thu, 8 May 2025 11:52:41 +0200 Subject: [PATCH 09/10] Fix adding items of the same name on frontend --- .../easyeat/ui/shopping_list/ShoppingListFragment.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt index 7fd4d67..7267253 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/ui/shopping_list/ShoppingListFragment.kt @@ -148,6 +148,16 @@ class ShoppingListFragment : Fragment() { 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) } } From 73bc290b6a6e927a73f47e30f5d6dec0fbd309a9 Mon Sep 17 00:00:00 2001 From: Jeas0001 Date: Thu, 8 May 2025 12:00:17 +0200 Subject: [PATCH 10/10] add item to shopping list new item in g and old item in kg --- backend/API/BusinessLogic/ShoppingListLogic.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backend/API/BusinessLogic/ShoppingListLogic.cs b/backend/API/BusinessLogic/ShoppingListLogic.cs index 129a6d7..bcadc5c 100644 --- a/backend/API/BusinessLogic/ShoppingListLogic.cs +++ b/backend/API/BusinessLogic/ShoppingListLogic.cs @@ -160,16 +160,31 @@ namespace API.BusinessLogic 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;