From 01f5efa991fae7d1165499ea1e6d2fadf8f3df10 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:20:51 -0600 Subject: [PATCH] added bulk delete for ProductSupplier, appointments, and adoptions --- .../adapters/AdoptionAdapter.java | 57 +++++- .../adapters/AppointmentAdapter.java | 57 +++++- .../adapters/InventoryAdapter.java | 12 +- .../petstoremobile/adapters/PetAdapter.java | 14 +- .../adapters/ProductSupplierAdapter.java | 65 ++++++- .../adapters/ServiceAdapter.java | 12 +- .../adapters/SupplierAdapter.java | 12 +- .../petstoremobile/api/AdoptionApi.java | 6 +- .../petstoremobile/api/AppointmentApi.java | 5 + .../api/ProductSupplierApi.java | 3 + .../dtos/BulkDeleteRequest.java | 8 +- .../listfragments/AdoptionFragment.java | 25 ++- .../listfragments/AppointmentFragment.java | 23 +++ .../listfragments/InventoryFragment.java | 5 +- .../fragments/listfragments/PetFragment.java | 5 +- .../ProductSupplierFragment.java | 23 +++ .../repositories/AdoptionRepository.java | 8 + .../repositories/AppointmentRepository.java | 8 + .../ProductSupplierRepository.java | 5 + .../repositories/PurchaseOrderRepository.java | 14 +- .../utils/BulkDeleteHandler.java | 17 +- .../petstoremobile/utils/SelectionHelper.java | 34 ++-- .../viewmodels/AdoptionViewModel.java | 10 + .../viewmodels/AppointmentViewModel.java | 10 + .../viewmodels/InventoryViewModel.java | 2 +- .../viewmodels/PetViewModel.java | 2 +- .../viewmodels/ProductSupplierViewModel.java | 7 + .../viewmodels/ServiceViewModel.java | 2 +- .../viewmodels/SupplierViewModel.java | 2 +- .../src/main/res/layout/fragment_adoption.xml | 31 +++ .../main/res/layout/fragment_appointment.xml | 31 +++ .../res/layout/fragment_product_supplier.xml | 31 +++ .../app/src/main/res/layout/item_adoption.xml | 177 ++++++++++-------- .../src/main/res/layout/item_appointment.xml | 144 +++++++------- .../main/res/layout/item_product_supplier.xml | 89 +++++---- 35 files changed, 695 insertions(+), 261 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/AdoptionAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/AdoptionAdapter.java index 259bde61..8a8c3979 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/AdoptionAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/AdoptionAdapter.java @@ -2,25 +2,51 @@ package com.example.petstoremobile.adapters; import android.graphics.Color; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.example.petstoremobile.databinding.ItemAdoptionBinding; import com.example.petstoremobile.dtos.AdoptionDTO; +import com.example.petstoremobile.utils.BulkDeleteHandler; +import com.example.petstoremobile.utils.SelectionHelper; import java.util.List; -public class AdoptionAdapter extends RecyclerView.Adapter { +public class AdoptionAdapter extends RecyclerView.Adapter implements BulkDeleteHandler.SelectableAdapter { private List adoptionList; private OnAdoptionClickListener listener; + private final SelectionHelper selectionHelper; public interface OnAdoptionClickListener { void onAdoptionClick(int position); + void onSelectionChanged(int count); } public AdoptionAdapter(List adoptionList, OnAdoptionClickListener listener) { this.adoptionList = adoptionList; this.listener = listener; + this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() { + @Override + public void onSelectionChanged(int count) { + listener.onSelectionChanged(count); + } + + @Override + public void onSelectionModeToggle(boolean selectionMode) { + notifyDataSetChanged(); + } + }); + } + + @Override + public List getSelectedKeys() { + return selectionHelper.getSelectedKeys(); + } + + @Override + public void clearSelection() { + selectionHelper.clearSelection(); } public static class AdoptionViewHolder extends RecyclerView.ViewHolder { @@ -68,9 +94,34 @@ public class AdoptionAdapter extends RecyclerView.Adapter listener.onAdoptionClick(position)); + String key = String.valueOf(a.getAdoptionId()); + + // Bulk delete selection mode + if (selectionHelper.isInSelectionMode()) { + binding.cbSelectAdoption.setVisibility(View.VISIBLE); + binding.cbSelectAdoption.setChecked(selectionHelper.isSelected(key)); + } else { + binding.cbSelectAdoption.setVisibility(View.GONE); + binding.cbSelectAdoption.setChecked(false); + } + + holder.itemView.setOnClickListener(v -> { + if (selectionHelper.isInSelectionMode()) { + selectionHelper.toggleSelection(key); + notifyItemChanged(position); + } else { + listener.onAdoptionClick(position); + } + }); + + holder.itemView.setOnLongClickListener(v -> { + if (!selectionHelper.isInSelectionMode()) { + selectionHelper.startSelection(key); + } + return true; + }); } @Override public int getItemCount() { return adoptionList.size(); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/AppointmentAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/AppointmentAdapter.java index 1cbcad91..cb292c1a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/AppointmentAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/AppointmentAdapter.java @@ -2,26 +2,52 @@ package com.example.petstoremobile.adapters; import android.graphics.Color; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.example.petstoremobile.databinding.ItemAppointmentBinding; import com.example.petstoremobile.dtos.AppointmentDTO; +import com.example.petstoremobile.utils.BulkDeleteHandler; +import com.example.petstoremobile.utils.SelectionHelper; import java.util.List; -public class AppointmentAdapter extends RecyclerView.Adapter { +public class AppointmentAdapter extends RecyclerView.Adapter implements BulkDeleteHandler.SelectableAdapter { private List appointmentList; private OnAppointmentClickListener appointmentClickListener; + private final SelectionHelper selectionHelper; public interface OnAppointmentClickListener { void onAppointmentClick(int position); + void onSelectionChanged(int count); } public AppointmentAdapter(List appointmentList, OnAppointmentClickListener appointmentClickListener) { this.appointmentList = appointmentList; this.appointmentClickListener = appointmentClickListener; + this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() { + @Override + public void onSelectionChanged(int count) { + appointmentClickListener.onSelectionChanged(count); + } + + @Override + public void onSelectionModeToggle(boolean selectionMode) { + notifyDataSetChanged(); + } + }); + } + + @Override + public List getSelectedKeys() { + return selectionHelper.getSelectedKeys(); + } + + @Override + public void clearSelection() { + selectionHelper.clearSelection(); } public static class AppointmentViewHolder extends RecyclerView.ViewHolder { @@ -70,11 +96,36 @@ public class AppointmentAdapter extends RecyclerView.Adapter appointmentClickListener.onAppointmentClick(position)); + String key = String.valueOf(a.getAppointmentId()); + + // Bulk delete selection mode + if (selectionHelper.isInSelectionMode()) { + binding.cbSelectAppointment.setVisibility(View.VISIBLE); + binding.cbSelectAppointment.setChecked(selectionHelper.isSelected(key)); + } else { + binding.cbSelectAppointment.setVisibility(View.GONE); + binding.cbSelectAppointment.setChecked(false); + } + + holder.itemView.setOnClickListener(v -> { + if (selectionHelper.isInSelectionMode()) { + selectionHelper.toggleSelection(key); + notifyItemChanged(position); + } else { + appointmentClickListener.onAppointmentClick(position); + } + }); + + holder.itemView.setOnLongClickListener(v -> { + if (!selectionHelper.isInSelectionMode()) { + selectionHelper.startSelection(key); + } + return true; + }); } @Override public int getItemCount() { return appointmentList.size(); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/InventoryAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/InventoryAdapter.java index 40b7fe41..9fbc1481 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/InventoryAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/InventoryAdapter.java @@ -44,8 +44,8 @@ public class InventoryAdapter extends RecyclerView.Adapter getSelectedIds() { - return selectionHelper.getSelectedIds(); + public List getSelectedKeys() { + return selectionHelper.getSelectedKeys(); } @Override @@ -91,10 +91,12 @@ public class InventoryAdapter extends RecyclerView.Adapter { if (selectionHelper.isInSelectionMode()) { - selectionHelper.toggleSelection(inv.getInventoryId()); + selectionHelper.toggleSelection(key); notifyItemChanged(position); } else { clickListener.onInventoryClick(holder.getAdapterPosition()); @@ -111,7 +113,7 @@ public class InventoryAdapter extends RecyclerView.Adapter { if (!selectionHelper.isInSelectionMode()) { - selectionHelper.startSelection(inv.getInventoryId()); + selectionHelper.startSelection(key); } return true; }); diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/PetAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/PetAdapter.java index 87dd95b5..7ec19b8a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/PetAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/PetAdapter.java @@ -57,8 +57,8 @@ public class PetAdapter extends RecyclerView.Adapter i } @Override - public List getSelectedIds() { - return selectionHelper.getSelectedIds(); + public List getSelectedKeys() { + return selectionHelper.getSelectedKeys(); } @Override @@ -118,10 +118,12 @@ public class PetAdapter extends RecyclerView.Adapter i binding.ivPetProfile.setImageResource(R.drawable.placeholder); } + String key = String.valueOf(pet.getPetId()); + // Bulk delete selection mode if (selectionHelper.isInSelectionMode()) { binding.cbSelectPet.setVisibility(View.VISIBLE); - binding.cbSelectPet.setChecked(selectionHelper.isSelected(pet.getPetId())); + binding.cbSelectPet.setChecked(selectionHelper.isSelected(key)); } else { binding.cbSelectPet.setVisibility(View.GONE); binding.cbSelectPet.setChecked(false); @@ -130,7 +132,7 @@ public class PetAdapter extends RecyclerView.Adapter i //when a row is clicked, open the detail view holder.itemView.setOnClickListener(v -> { if (selectionHelper.isInSelectionMode()) { - selectionHelper.toggleSelection(pet.getPetId()); + selectionHelper.toggleSelection(key); notifyItemChanged(position); } else { petClickListener.onPetClick(position); @@ -139,7 +141,7 @@ public class PetAdapter extends RecyclerView.Adapter i holder.itemView.setOnLongClickListener(v -> { if (!selectionHelper.isInSelectionMode()) { - selectionHelper.startSelection(pet.getPetId()); + selectionHelper.startSelection(key); } return true; }); @@ -149,4 +151,4 @@ public class PetAdapter extends RecyclerView.Adapter i public int getItemCount() { return petList.size(); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/ProductSupplierAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/ProductSupplierAdapter.java index 75519120..231af741 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/ProductSupplierAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/ProductSupplierAdapter.java @@ -1,25 +1,54 @@ package com.example.petstoremobile.adapters; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; + import com.example.petstoremobile.databinding.ItemProductSupplierBinding; import com.example.petstoremobile.dtos.ProductSupplierDTO; +import com.example.petstoremobile.utils.BulkDeleteHandler; +import com.example.petstoremobile.utils.SelectionHelper; + import java.util.List; -public class ProductSupplierAdapter extends RecyclerView.Adapter { +public class ProductSupplierAdapter extends RecyclerView.Adapter implements BulkDeleteHandler.SelectableAdapter { - private List list; - private OnProductSupplierClickListener listener; + private final List list; + private final OnProductSupplierClickListener listener; + private final SelectionHelper selectionHelper; public interface OnProductSupplierClickListener { void onProductSupplierClick(int position); + void onSelectionChanged(int count); } public ProductSupplierAdapter(List list, OnProductSupplierClickListener listener) { this.list = list; this.listener = listener; + this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() { + @Override + public void onSelectionChanged(int count) { + listener.onSelectionChanged(count); + } + + @Override + public void onSelectionModeToggle(boolean selectionMode) { + notifyDataSetChanged(); + } + }); + } + + @Override + public List getSelectedKeys() { + return selectionHelper.getSelectedKeys(); + } + + @Override + public void clearSelection() { + selectionHelper.clearSelection(); } public static class PSViewHolder extends RecyclerView.ViewHolder { @@ -46,9 +75,35 @@ public class ProductSupplierAdapter extends RecyclerView.Adapter listener.onProductSupplierClick(position)); + + String key = ps.getProductId() + "-" + ps.getSupplierId(); + + // Bulk delete selection mode + if (selectionHelper.isInSelectionMode()) { + binding.cbSelectProductSupplier.setVisibility(View.VISIBLE); + binding.cbSelectProductSupplier.setChecked(selectionHelper.isSelected(key)); + } else { + binding.cbSelectProductSupplier.setVisibility(View.GONE); + binding.cbSelectProductSupplier.setChecked(false); + } + + holder.itemView.setOnClickListener(v -> { + if (selectionHelper.isInSelectionMode()) { + selectionHelper.toggleSelection(key); + notifyItemChanged(position); + } else { + listener.onProductSupplierClick(position); + } + }); + + holder.itemView.setOnLongClickListener(v -> { + if (!selectionHelper.isInSelectionMode()) { + selectionHelper.startSelection(key); + } + return true; + }); } @Override public int getItemCount() { return list.size(); } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/ServiceAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/ServiceAdapter.java index 8ac809d2..30a5b8d7 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/ServiceAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/ServiceAdapter.java @@ -48,8 +48,8 @@ public class ServiceAdapter extends RecyclerView.Adapter getSelectedIds() { - return selectionHelper.getSelectedIds(); + public List getSelectedKeys() { + return selectionHelper.getSelectedKeys(); } @Override @@ -88,10 +88,12 @@ public class ServiceAdapter extends RecyclerView.Adapter { if (selectionHelper.isInSelectionMode()) { - selectionHelper.toggleSelection(service.getServiceId()); + selectionHelper.toggleSelection(key); notifyItemChanged(position); } else { clickListener.onServiceClick(position); @@ -108,7 +110,7 @@ public class ServiceAdapter extends RecyclerView.Adapter { if (!selectionHelper.isInSelectionMode()) { - selectionHelper.startSelection(service.getServiceId()); + selectionHelper.startSelection(key); } return true; }); diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/SupplierAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/SupplierAdapter.java index d932044b..980dc071 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/SupplierAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/SupplierAdapter.java @@ -44,8 +44,8 @@ public class SupplierAdapter extends RecyclerView.Adapter getSelectedIds() { - return selectionHelper.getSelectedIds(); + public List getSelectedKeys() { + return selectionHelper.getSelectedKeys(); } @Override @@ -82,10 +82,12 @@ public class SupplierAdapter extends RecyclerView.Adapter { if (selectionHelper.isInSelectionMode()) { - selectionHelper.toggleSelection(supplier.getSupId()); + selectionHelper.toggleSelection(key); notifyItemChanged(position); } else { supplierClickListener.onSupplierClick(position); @@ -103,7 +105,7 @@ public class SupplierAdapter extends RecyclerView.Adapter { if (!selectionHelper.isInSelectionMode()) { - selectionHelper.startSelection(supplier.getSupId()); + selectionHelper.startSelection(key); } return true; }); diff --git a/android/app/src/main/java/com/example/petstoremobile/api/AdoptionApi.java b/android/app/src/main/java/com/example/petstoremobile/api/AdoptionApi.java index 2f704a41..cd27f74b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/AdoptionApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/AdoptionApi.java @@ -1,12 +1,14 @@ package com.example.petstoremobile.api; import com.example.petstoremobile.dtos.AdoptionDTO; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.GET; +import retrofit2.http.HTTP; import retrofit2.http.POST; import retrofit2.http.PUT; import retrofit2.http.Path; @@ -30,5 +32,7 @@ public interface AdoptionApi { @DELETE("api/v1/adoptions/{id}") Call deleteAdoption(@Path("id") Long id); -} + @HTTP(method = "DELETE", path = "api/v1/adoptions", hasBody = true) + Call bulkDeleteAdoptions(@Body BulkDeleteRequest request); +} diff --git a/android/app/src/main/java/com/example/petstoremobile/api/AppointmentApi.java b/android/app/src/main/java/com/example/petstoremobile/api/AppointmentApi.java index d811b2e0..5b8a37a7 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/AppointmentApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/AppointmentApi.java @@ -1,12 +1,14 @@ package com.example.petstoremobile.api; import com.example.petstoremobile.dtos.AppointmentDTO; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.GET; +import retrofit2.http.HTTP; import retrofit2.http.POST; import retrofit2.http.PUT; import retrofit2.http.Path; @@ -35,4 +37,7 @@ public interface AppointmentApi { @DELETE("api/v1/appointments/{id}") Call deleteAppointment(@Path("id") Long id); + + @HTTP(method = "DELETE", path = "api/v1/appointments", hasBody = true) + Call bulkDeleteAppointments(@Body BulkDeleteRequest request); } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/api/ProductSupplierApi.java b/android/app/src/main/java/com/example/petstoremobile/api/ProductSupplierApi.java index 67a0e7f2..b4414be5 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/ProductSupplierApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/ProductSupplierApi.java @@ -1,5 +1,6 @@ package com.example.petstoremobile.api; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.ProductSupplierDTO; import retrofit2.Call; @@ -34,4 +35,6 @@ public interface ProductSupplierApi { Call deleteProductSupplier( @Path("productId") Long productId, @Path("supplierId") Long supplierId); + @HTTP(method = "DELETE", path = "api/v1/product-suppliers", hasBody = true) + Call bulkDeleteProductSuppliers(@Body BulkDeleteRequest request); } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/BulkDeleteRequest.java b/android/app/src/main/java/com/example/petstoremobile/dtos/BulkDeleteRequest.java index 49f92f06..e53c8369 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/BulkDeleteRequest.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/BulkDeleteRequest.java @@ -3,20 +3,20 @@ package com.example.petstoremobile.dtos; import java.util.List; public class BulkDeleteRequest { - private List ids; + private List ids; public BulkDeleteRequest() { } - public BulkDeleteRequest(List ids) { + public BulkDeleteRequest(List ids) { this.ids = ids; } - public List getIds() { + public List getIds() { return ids; } - public void setIds(List ids) { + public void setIds(List ids) { this.ids = ids; } } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java index fafd0d93..74c54c50 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java @@ -17,11 +17,12 @@ import com.example.petstoremobile.adapters.AdoptionAdapter; import com.example.petstoremobile.databinding.FragmentAdoptionBinding; import com.example.petstoremobile.dtos.AdoptionDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.viewmodels.AdoptionViewModel; import com.example.petstoremobile.utils.EventDecorator; +import com.example.petstoremobile.utils.Resource; import com.prolificinteractive.materialcalendarview.CalendarDay; import com.prolificinteractive.materialcalendarview.CalendarMode; -import com.prolificinteractive.materialcalendarview.MaterialCalendarView; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; @@ -36,6 +37,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop private List filteredList = new ArrayList<>(); private AdoptionAdapter adapter; private AdoptionViewModel viewModel; + private BulkDeleteHandler bulkDeleteHandler; private CalendarDay selectedCalendarDay; private boolean isMonthMode = false; private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); @@ -61,6 +63,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop setupSearch(); setupSwipeRefresh(); setupCalendar(); + setupBulkDelete(); loadAdoptions(); binding.fabAddAdoption.setOnClickListener(v -> openDetail(-1)); @@ -80,6 +83,19 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop return binding.getRoot(); } + private void setupBulkDelete() { + bulkDeleteHandler = new BulkDeleteHandler( + this, + binding.layoutBulkDelete, + binding.tvSelectionCount, + binding.btnBulkDelete, + adapter, + "adoption", + viewModel::bulkDeleteAdoptions, + this::loadAdoptions + ); + } + @Override public void onDestroyView() { super.onDestroyView(); @@ -249,4 +265,11 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop */ @Override public void onAdoptionClick(int position) { openDetail(position); } + + @Override + public void onSelectionChanged(int selectedCount) { + if (bulkDeleteHandler != null) { + bulkDeleteHandler.onSelectionChanged(selectedCount); + } + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java index 3724850b..de212485 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java @@ -26,6 +26,7 @@ import com.example.petstoremobile.databinding.FragmentAppointmentBinding; import com.example.petstoremobile.dtos.AppointmentDTO; import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.viewmodels.AppointmentViewModel; @@ -57,6 +58,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. private AppointmentViewModel appointmentViewModel; private StoreViewModel storeViewModel; private AuthViewModel authViewModel; + private BulkDeleteHandler bulkDeleteHandler; private CalendarDay selectedCalendarDay; private boolean isMonthMode = false; @@ -90,6 +92,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. setupCalendar(); setupFilterToggle(); setupMyAppointmentFilter(); + setupBulkDelete(); binding.fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1)); @@ -110,6 +113,19 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. return binding.getRoot(); } + private void setupBulkDelete() { + bulkDeleteHandler = new BulkDeleteHandler( + this, + binding.layoutBulkDelete, + binding.tvSelectionCount, + binding.btnBulkDelete, + adapter, + "appointment", + appointmentViewModel::bulkDeleteAppointments, + this::loadAppointmentData + ); + } + @Override public void onDestroyView() { super.onDestroyView(); @@ -303,6 +319,13 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. openAppointmentDetails(position); } + @Override + public void onSelectionChanged(int count) { + if (bulkDeleteHandler != null) { + bulkDeleteHandler.onSelectionChanged(count); + } + } + /** * Fetches appointment data from the server with all active filters. */ diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java index 0c0e07de..378ec0bb 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java @@ -99,10 +99,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn binding.layoutBulkDelete, binding.tvSelectionCount, binding.btnBulkDelete, - new BulkDeleteHandler.SelectableAdapter() { - @Override public List getSelectedIds() { return adapter.getSelectedIds(); } - @Override public void clearSelection() { adapter.clearSelection(); } - }, + adapter, "inventory item", viewModel::bulkDeleteInventory, () -> loadInventory(true) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java index d0fdfa3e..4daa8ea3 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java @@ -99,10 +99,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen binding.layoutBulkDelete, binding.tvSelectionCount, binding.btnBulkDelete, - new BulkDeleteHandler.SelectableAdapter() { - @Override public List getSelectedIds() { return adapter.getSelectedIds(); } - @Override public void clearSelection() { adapter.clearSelection(); } - }, + adapter, "pet", viewModel::bulkDeletePets, this::loadPetData diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java index 1c0a9c19..11f5ebf1 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductSupplierFragment.java @@ -24,6 +24,7 @@ import com.example.petstoremobile.dtos.ProductDTO; import com.example.petstoremobile.dtos.ProductSupplierDTO; import com.example.petstoremobile.dtos.SupplierDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.viewmodels.ProductSupplierViewModel; @@ -48,6 +49,7 @@ public class ProductSupplierFragment extends Fragment private ProductSupplierViewModel viewModel; private ProductViewModel productViewModel; private SupplierViewModel supplierViewModel; + private BulkDeleteHandler bulkDeleteHandler; /** * Initializes the fragment and its associated ViewModels. @@ -74,6 +76,7 @@ public class ProductSupplierFragment extends Fragment setupSupplierFilter(); setupSwipeRefresh(); setupFilterToggle(); + setupBulkDelete(); binding.fabAddPS.setOnClickListener(v -> openDetail(-1)); @@ -90,6 +93,19 @@ public class ProductSupplierFragment extends Fragment return binding.getRoot(); } + private void setupBulkDelete() { + bulkDeleteHandler = new BulkDeleteHandler( + this, + binding.layoutBulkDelete, + binding.tvSelectionCount, + binding.btnBulkDelete, + adapter, + "relationship", + viewModel::bulkDeleteProductSuppliers, + this::loadData + ); + } + @Override public void onDestroyView() { super.onDestroyView(); @@ -265,4 +281,11 @@ public class ProductSupplierFragment extends Fragment */ @Override public void onProductSupplierClick(int position) { openDetail(position); } + + @Override + public void onSelectionChanged(int count) { + if (bulkDeleteHandler != null) { + bulkDeleteHandler.onSelectionChanged(count); + } + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java index 0e73b706..53d34c9f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AdoptionRepository.java @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData; import com.example.petstoremobile.api.AdoptionApi; import com.example.petstoremobile.dtos.AdoptionDTO; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.utils.Resource; @@ -54,4 +55,11 @@ public class AdoptionRepository extends BaseRepository { public LiveData> deleteAdoption(Long id) { return executeCall(adoptionApi.deleteAdoption(id)); } + + /** + * Sends a request to the API to delete multiple adoption records. + */ + public LiveData> bulkDeleteAdoptions(BulkDeleteRequest request) { + return executeCall(adoptionApi.bulkDeleteAdoptions(request)); + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java index 1c85e91a..85083a25 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AppointmentRepository.java @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData; import com.example.petstoremobile.api.AppointmentApi; import com.example.petstoremobile.dtos.AppointmentDTO; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.utils.Resource; @@ -54,4 +55,11 @@ public class AppointmentRepository extends BaseRepository { public LiveData> deleteAppointment(Long id) { return executeCall(appointmentApi.deleteAppointment(id)); } + + /** + * Sends a request to the API to delete multiple appointment records. + */ + public LiveData> bulkDeleteAppointments(BulkDeleteRequest request) { + return executeCall(appointmentApi.bulkDeleteAppointments(request)); + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java index e5c135a5..9b2f8df3 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ProductSupplierRepository.java @@ -3,6 +3,7 @@ package com.example.petstoremobile.repositories; import androidx.lifecycle.LiveData; import com.example.petstoremobile.api.ProductSupplierApi; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.ProductSupplierDTO; import com.example.petstoremobile.utils.Resource; @@ -54,4 +55,8 @@ public class ProductSupplierRepository extends BaseRepository { public LiveData> deleteProductSupplier(Long productId, Long supplierId) { return executeCall(api.deleteProductSupplier(productId, supplierId)); } + + public LiveData> bulkDeleteProductSuppliers(BulkDeleteRequest request) { + return executeCall(api.bulkDeleteProductSuppliers(request)); + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java index b55f9a33..dd9bd637 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java @@ -12,25 +12,25 @@ import javax.inject.Singleton; @Singleton public class PurchaseOrderRepository extends BaseRepository { - private final PurchaseOrderApi api; + private final PurchaseOrderApi purchaseOrderApi; @Inject - public PurchaseOrderRepository(PurchaseOrderApi api) { - super("PurchaseOrderRepo"); - this.api = api; + public PurchaseOrderRepository(PurchaseOrderApi purchaseOrderApi) { + super("PurchaseOrderRepository"); + this.purchaseOrderApi = purchaseOrderApi; } /** * Retrieves a paginated list of all purchase orders from the API. */ public LiveData>> getAllPurchaseOrders(int page, int size, String query, Long storeId, String sort) { - return executeCall(api.getAllPurchaseOrders(page, size, query, storeId, sort)); + return executeCall(purchaseOrderApi.getAllPurchaseOrders(page, size, query, storeId, sort)); } /** * Retrieves a specific purchase order by its ID from the API. */ public LiveData> getPurchaseOrderById(Long id) { - return executeCall(api.getPurchaseOrderById(id)); + return executeCall(purchaseOrderApi.getPurchaseOrderById(id)); } -} +} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/BulkDeleteHandler.java b/android/app/src/main/java/com/example/petstoremobile/utils/BulkDeleteHandler.java index 1e777f5d..fe67282b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/BulkDeleteHandler.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/BulkDeleteHandler.java @@ -12,6 +12,7 @@ import java.util.List; /** * A helper class to handle the UI and logic for bulk deletion across different fragments. + * Now supports String keys to accommodate both simple and composite keys. */ public class BulkDeleteHandler { @@ -19,7 +20,7 @@ public class BulkDeleteHandler { * Interface that adapters must implement to support bulk selection. */ public interface SelectableAdapter { - List getSelectedIds(); + List getSelectedKeys(); void clearSelection(); } @@ -27,7 +28,7 @@ public class BulkDeleteHandler { * Functional interface for the API call execution. */ public interface BulkDeleteOperation { - LiveData> execute(List ids); + LiveData> execute(List keys); } private final Fragment fragment; @@ -82,23 +83,23 @@ public class BulkDeleteHandler { * Shows the confirmation dialog. */ private void confirmDelete() { - List ids = adapter.getSelectedIds(); - if (ids.isEmpty()) return; + List keys = adapter.getSelectedKeys(); + if (keys.isEmpty()) return; - DialogUtils.showBulkDeleteConfirmDialog(fragment.requireContext(), ids.size(), () -> performDelete(ids)); + DialogUtils.showBulkDeleteConfirmDialog(fragment.requireContext(), keys.size(), () -> performDelete(keys)); } /** * Executes the deletion via the provided operation. */ - private void performDelete(List ids) { - operation.execute(ids).observe(fragment.getViewLifecycleOwner(), resource -> { + private void performDelete(List keys) { + operation.execute(keys).observe(fragment.getViewLifecycleOwner(), resource -> { if (resource != null && resource.status != Resource.Status.LOADING) { if (resource.status == Resource.Status.SUCCESS) { adapter.clearSelection(); hideBar(); onSuccess.run(); - Toast.makeText(fragment.getContext(), ids.size() + " " + itemName + "(s) deleted", Toast.LENGTH_SHORT).show(); + Toast.makeText(fragment.getContext(), keys.size() + " " + itemName + "(s) deleted", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(fragment.getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/SelectionHelper.java b/android/app/src/main/java/com/example/petstoremobile/utils/SelectionHelper.java index cbda2267..197cb557 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/SelectionHelper.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/SelectionHelper.java @@ -5,10 +5,11 @@ import java.util.List; /** * Helper class to manage selection state in Adapters for bulk operations. + * Uses String keys to support both simple Long IDs and composite keys (e.g., "id1-id2"). */ public class SelectionHelper { - private final List selectedIds = new ArrayList<>(); + private final List selectedKeys = new ArrayList<>(); private boolean selectionMode = false; private final SelectionListener listener; @@ -21,44 +22,45 @@ public class SelectionHelper { this.listener = listener; } - public void toggleSelection(Long id) { - if (id == null) return; + public void toggleSelection(String key) { + if (key == null) return; - if (selectedIds.contains(id)) { - selectedIds.remove(id); + if (selectedKeys.contains(key)) { + selectedKeys.remove(key); } else { - selectedIds.add(id); + selectedKeys.add(key); } - listener.onSelectionChanged(selectedIds.size()); + listener.onSelectionChanged(selectedKeys.size()); - if (selectedIds.isEmpty() && selectionMode) { + if (selectedKeys.isEmpty() && selectionMode) { selectionMode = false; listener.onSelectionModeToggle(false); } } - public void startSelection(Long id) { + public void startSelection(String key) { + if (key == null) return; selectionMode = true; - selectedIds.add(id); - listener.onSelectionChanged(selectedIds.size()); + selectedKeys.add(key); + listener.onSelectionChanged(selectedKeys.size()); listener.onSelectionModeToggle(true); } - public boolean isSelected(Long id) { - return selectedIds.contains(id); + public boolean isSelected(String key) { + return selectedKeys.contains(key); } public boolean isInSelectionMode() { return selectionMode; } - public List getSelectedIds() { - return new ArrayList<>(selectedIds); + public List getSelectedKeys() { + return new ArrayList<>(selectedKeys); } public void clearSelection() { - selectedIds.clear(); + selectedKeys.clear(); selectionMode = false; listener.onSelectionChanged(0); listener.onSelectionModeToggle(false); diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionViewModel.java index 039cef30..0287c85c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionViewModel.java @@ -4,10 +4,13 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; import com.example.petstoremobile.dtos.AdoptionDTO; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.repositories.AdoptionRepository; import com.example.petstoremobile.utils.Resource; +import java.util.List; + import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; @@ -55,4 +58,11 @@ public class AdoptionViewModel extends ViewModel { public LiveData> deleteAdoption(Long id) { return repository.deleteAdoption(id); } + + /** + * Deletes multiple adoption records. + */ + public LiveData> bulkDeleteAdoptions(List ids) { + return repository.bulkDeleteAdoptions(new BulkDeleteRequest(ids)); + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentViewModel.java index 913d7ab2..69f24c95 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentViewModel.java @@ -4,10 +4,13 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; import com.example.petstoremobile.dtos.AppointmentDTO; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.repositories.AppointmentRepository; import com.example.petstoremobile.utils.Resource; +import java.util.List; + import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; @@ -55,4 +58,11 @@ public class AppointmentViewModel extends ViewModel { public LiveData> deleteAppointment(Long id) { return repository.deleteAppointment(id); } + + /** + * Deletes multiple appointment records. + */ + public LiveData> bulkDeleteAppointments(List ids) { + return repository.bulkDeleteAppointments(new BulkDeleteRequest(ids)); + } } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java index 3b5a8507..cccbec89 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java @@ -70,7 +70,7 @@ public class InventoryViewModel extends ViewModel { /** * Deletes multiple inventory records in a single request. */ - public LiveData> bulkDeleteInventory(List ids) { + public LiveData> bulkDeleteInventory(List ids) { return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java index 4866b79a..c75926a7 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java @@ -63,7 +63,7 @@ public class PetViewModel extends ViewModel { /** * Deletes multiple pet records. */ - public LiveData> bulkDeletePets(List ids) { + public LiveData> bulkDeletePets(List ids) { return repository.bulkDeletePets(new BulkDeleteRequest(ids)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierViewModel.java index 95613929..f4302225 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierViewModel.java @@ -3,11 +3,14 @@ package com.example.petstoremobile.viewmodels; import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; +import com.example.petstoremobile.dtos.BulkDeleteRequest; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.ProductSupplierDTO; import com.example.petstoremobile.repositories.ProductSupplierRepository; import com.example.petstoremobile.utils.Resource; +import java.util.List; + import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; @@ -48,4 +51,8 @@ public class ProductSupplierViewModel extends ViewModel { public LiveData> deleteProductSupplier(Long productId, Long supplierId) { return repository.deleteProductSupplier(productId, supplierId); } + + public LiveData> bulkDeleteProductSuppliers(List ids) { + return repository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids)); + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceViewModel.java index 3b74b047..ebd5c3b6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceViewModel.java @@ -62,7 +62,7 @@ public class ServiceViewModel extends ViewModel { /** * Deletes multiple services. */ - public LiveData> bulkDeleteServices(List ids) { + public LiveData> bulkDeleteServices(List ids) { return repository.bulkDeleteServices(new BulkDeleteRequest(ids)); } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierViewModel.java index dbffaffc..1486a562 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierViewModel.java @@ -62,7 +62,7 @@ public class SupplierViewModel extends ViewModel { /** * Deletes multiple supplier records. */ - public LiveData> bulkDeleteSuppliers(List ids) { + public LiveData> bulkDeleteSuppliers(List ids) { return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids)); } } diff --git a/android/app/src/main/res/layout/fragment_adoption.xml b/android/app/src/main/res/layout/fragment_adoption.xml index 5bc95c38..5004f137 100644 --- a/android/app/src/main/res/layout/fragment_adoption.xml +++ b/android/app/src/main/res/layout/fragment_adoption.xml @@ -70,6 +70,37 @@ android:padding="12dp" android:textColor="@color/text_dark"/> + + + + +