added helper class for bulk delete and mad pets have bulk delete
This commit is contained in:
@@ -10,16 +10,16 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.example.petstoremobile.databinding.ItemInventoryBinding;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.SelectionHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> {
|
||||
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> implements BulkDeleteHandler.SelectableAdapter {
|
||||
|
||||
private final List<InventoryDTO> inventoryList;
|
||||
private final OnInventoryClickListener clickListener;
|
||||
private final List<Long> selectedIds = new ArrayList<>();
|
||||
private boolean selectionMode = false;
|
||||
private final SelectionHelper selectionHelper;
|
||||
|
||||
public interface OnInventoryClickListener {
|
||||
void onInventoryClick(int position);
|
||||
@@ -30,6 +30,27 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
|
||||
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
|
||||
this.inventoryList = inventoryList;
|
||||
this.clickListener = clickListener;
|
||||
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
|
||||
@Override
|
||||
public void onSelectionChanged(int count) {
|
||||
clickListener.onSelectionChanged(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionModeToggle(boolean selectionMode) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getSelectedIds() {
|
||||
return selectionHelper.getSelectedIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSelection() {
|
||||
selectionHelper.clearSelection();
|
||||
}
|
||||
|
||||
public static class InventoryViewHolder extends RecyclerView.ViewHolder {
|
||||
@@ -71,66 +92,33 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
|
||||
}
|
||||
|
||||
// Bulk delete selection mode
|
||||
if (selectionMode) {
|
||||
if (selectionHelper.isInSelectionMode()) {
|
||||
binding.cbSelectInventory.setVisibility(View.VISIBLE);
|
||||
binding.cbSelectInventory.setChecked(inv.getInventoryId() != null
|
||||
&& selectedIds.contains(inv.getInventoryId()));
|
||||
binding.cbSelectInventory.setChecked(selectionHelper.isSelected(inv.getInventoryId()));
|
||||
} else {
|
||||
binding.cbSelectInventory.setVisibility(View.GONE);
|
||||
binding.cbSelectInventory.setChecked(false);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (selectionMode) {
|
||||
toggleSelection(inv.getInventoryId(), binding.cbSelectInventory);
|
||||
if (selectionHelper.isInSelectionMode()) {
|
||||
selectionHelper.toggleSelection(inv.getInventoryId());
|
||||
notifyItemChanged(position);
|
||||
} else {
|
||||
clickListener.onInventoryClick(holder.getAdapterPosition());
|
||||
}
|
||||
});
|
||||
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
if (!selectionMode) {
|
||||
selectionMode = true;
|
||||
toggleSelection(inv.getInventoryId(), binding.cbSelectInventory);
|
||||
notifyDataSetChanged();
|
||||
if (!selectionHelper.isInSelectionMode()) {
|
||||
selectionHelper.startSelection(inv.getInventoryId());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleSelection(Long id, android.widget.CheckBox checkBox) {
|
||||
if (id == null)
|
||||
return;
|
||||
if (selectedIds.contains(id)) {
|
||||
selectedIds.remove(id);
|
||||
checkBox.setChecked(false);
|
||||
} else {
|
||||
selectedIds.add(id);
|
||||
checkBox.setChecked(true);
|
||||
}
|
||||
clickListener.onSelectionChanged(selectedIds.size());
|
||||
if (selectedIds.isEmpty()) {
|
||||
selectionMode = false;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Long> getSelectedIds() {
|
||||
return new ArrayList<>(selectedIds);
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
selectedIds.clear();
|
||||
selectionMode = false;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public boolean isInSelectionMode() {
|
||||
return selectionMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return inventoryList.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -10,25 +11,41 @@ import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.PetApi;
|
||||
import com.example.petstoremobile.databinding.ItemPetBinding;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.GlideUtils;
|
||||
import com.example.petstoremobile.utils.SelectionHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
||||
public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> implements BulkDeleteHandler.SelectableAdapter {
|
||||
|
||||
private List<PetDTO> petList;
|
||||
private OnPetClickListener petClickListener;
|
||||
private String baseUrl;
|
||||
private String token;
|
||||
private final SelectionHelper selectionHelper;
|
||||
|
||||
// Interface for pet click on recycler view
|
||||
public interface OnPetClickListener {
|
||||
void onPetClick(int position);
|
||||
void onSelectionChanged(int selectedCount);
|
||||
}
|
||||
|
||||
//Constructor
|
||||
public PetAdapter(List<PetDTO> petList, OnPetClickListener petClickListener) {
|
||||
this.petList = petList;
|
||||
this.petClickListener = petClickListener;
|
||||
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
|
||||
@Override
|
||||
public void onSelectionChanged(int count) {
|
||||
petClickListener.onSelectionChanged(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionModeToggle(boolean selectionMode) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
@@ -39,6 +56,16 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getSelectedIds() {
|
||||
return selectionHelper.getSelectedIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearSelection() {
|
||||
selectionHelper.clearSelection();
|
||||
}
|
||||
|
||||
// Get the controls of each row in recycler view
|
||||
public static class PetViewHolder extends RecyclerView.ViewHolder {
|
||||
private final ItemPetBinding binding;
|
||||
@@ -91,8 +118,31 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
||||
binding.ivPetProfile.setImageResource(R.drawable.placeholder);
|
||||
}
|
||||
|
||||
// Bulk delete selection mode
|
||||
if (selectionHelper.isInSelectionMode()) {
|
||||
binding.cbSelectPet.setVisibility(View.VISIBLE);
|
||||
binding.cbSelectPet.setChecked(selectionHelper.isSelected(pet.getPetId()));
|
||||
} else {
|
||||
binding.cbSelectPet.setVisibility(View.GONE);
|
||||
binding.cbSelectPet.setChecked(false);
|
||||
}
|
||||
|
||||
//when a row is clicked, open the detail view
|
||||
holder.itemView.setOnClickListener(v -> petClickListener.onPetClick(position));
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (selectionHelper.isInSelectionMode()) {
|
||||
selectionHelper.toggleSelection(pet.getPetId());
|
||||
notifyItemChanged(position);
|
||||
} else {
|
||||
petClickListener.onPetClick(position);
|
||||
}
|
||||
});
|
||||
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
if (!selectionHelper.isInSelectionMode()) {
|
||||
selectionHelper.startSelection(pet.getPetId());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.PetDTO;
|
||||
|
||||
@@ -8,6 +9,7 @@ import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.HTTP;
|
||||
import retrofit2.http.Multipart;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
@@ -48,6 +50,10 @@ public interface PetApi {
|
||||
@DELETE("api/v1/pets/{id}")
|
||||
Call<Void> deletePet(@Path("id") Long id);
|
||||
|
||||
// Bulk delete pets
|
||||
@HTTP(method = "DELETE", path = "api/v1/pets", hasBody = true)
|
||||
Call<Void> bulkDeletePets(@Body BulkDeleteRequest request);
|
||||
|
||||
// Upload pet image
|
||||
@Multipart
|
||||
@POST("api/v1/pets/{id}/image")
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.example.petstoremobile.databinding.FragmentInventoryBinding;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
@@ -44,6 +45,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private InventoryAdapter adapter;
|
||||
private InventoryViewModel viewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
// Pagination
|
||||
private int currentPage = 0;
|
||||
@@ -72,6 +74,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
setupStoreFilter();
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
setupBulkDelete();
|
||||
loadInventory(true);
|
||||
loadStoreData();
|
||||
|
||||
@@ -87,11 +90,25 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
}
|
||||
});
|
||||
|
||||
binding.btnBulkDelete.setOnClickListener(v -> confirmBulkDelete());
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
binding.layoutBulkDelete,
|
||||
binding.tvSelectionCount,
|
||||
binding.btnBulkDelete,
|
||||
new BulkDeleteHandler.SelectableAdapter() {
|
||||
@Override public List<Long> getSelectedIds() { return adapter.getSelectedIds(); }
|
||||
@Override public void clearSelection() { adapter.clearSelection(); }
|
||||
},
|
||||
"inventory item",
|
||||
viewModel::bulkDeleteInventory,
|
||||
() -> loadInventory(true)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -243,50 +260,6 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation dialog before performing a bulk deletion of selected items.
|
||||
*/
|
||||
private void confirmBulkDelete() {
|
||||
List<Long> ids = adapter.getSelectedIds();
|
||||
if (ids.isEmpty())
|
||||
return;
|
||||
|
||||
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete " + ids.size() + " item(s)?")
|
||||
.setMessage("This cannot be undone.")
|
||||
.setPositiveButton("Delete", (d, w) -> bulkDelete(ids))
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the bulk deletion of inventory items through the ViewModel.
|
||||
*/
|
||||
private void bulkDelete(List<Long> ids) {
|
||||
viewModel.bulkDeleteInventory(ids).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
adapter.clearSelection();
|
||||
hideBulkDeleteBar();
|
||||
loadInventory(true);
|
||||
Toast.makeText(getContext(), ids.size() + " item(s) deleted", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the bulk deletion UI bar.
|
||||
*/
|
||||
private void hideBulkDeleteBar() {
|
||||
if (binding != null) {
|
||||
binding.btnBulkDelete.setVisibility(View.GONE);
|
||||
binding.tvSelectionCount.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the inventory detail screen for a specific item or to add a new one.
|
||||
*/
|
||||
@@ -322,12 +295,8 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
*/
|
||||
@Override
|
||||
public void onSelectionChanged(int selectedCount) {
|
||||
if (selectedCount > 0) {
|
||||
binding.btnBulkDelete.setVisibility(View.VISIBLE);
|
||||
binding.tvSelectionCount.setVisibility(View.VISIBLE);
|
||||
binding.tvSelectionCount.setText(selectedCount + " selected");
|
||||
} else {
|
||||
hideBulkDeleteBar();
|
||||
if (bulkDeleteHandler != null) {
|
||||
bulkDeleteHandler.onSelectionChanged(selectedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.example.petstoremobile.databinding.FragmentPetBinding;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
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.PetViewModel;
|
||||
@@ -46,6 +47,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
private PetAdapter adapter;
|
||||
private PetViewModel viewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
@Inject @Named("baseUrl") String baseUrl;
|
||||
|
||||
@@ -74,6 +76,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
setupStoreFilter();
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
setupBulkDelete();
|
||||
|
||||
binding.fabAddPet.setOnClickListener(v -> openPetDetails());
|
||||
|
||||
@@ -90,6 +93,22 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
binding.layoutBulkDelete,
|
||||
binding.tvSelectionCount,
|
||||
binding.btnBulkDelete,
|
||||
new BulkDeleteHandler.SelectableAdapter() {
|
||||
@Override public List<Long> getSelectedIds() { return adapter.getSelectedIds(); }
|
||||
@Override public void clearSelection() { adapter.clearSelection(); }
|
||||
},
|
||||
"pet",
|
||||
viewModel::bulkDeletePets,
|
||||
this::loadPetData
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -231,6 +250,13 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
openPetProfile(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged(int selectedCount) {
|
||||
if (bulkDeleteHandler != null) {
|
||||
bulkDeleteHandler.onSelectionChanged(selectedCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches pet data from the server with all active filters.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.example.petstoremobile.repositories;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.example.petstoremobile.api.PetApi;
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
@@ -57,6 +58,13 @@ public class PetRepository extends BaseRepository {
|
||||
return executeCall(petApi.deletePet(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the API to delete multiple pet records.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeletePets(BulkDeleteRequest request) {
|
||||
return executeCall(petApi.bulkDeletePets(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads an image file for a specific pet via the API.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.example.petstoremobile.utils;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A helper class to handle the UI and logic for bulk deletion across different fragments.
|
||||
*/
|
||||
public class BulkDeleteHandler {
|
||||
|
||||
/**
|
||||
* Interface that adapters must implement to support bulk selection.
|
||||
*/
|
||||
public interface SelectableAdapter {
|
||||
List<Long> getSelectedIds();
|
||||
void clearSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface for the API call execution.
|
||||
*/
|
||||
public interface BulkDeleteOperation {
|
||||
LiveData<Resource<Void>> execute(List<Long> ids);
|
||||
}
|
||||
|
||||
private final Fragment fragment;
|
||||
private final View layoutBar;
|
||||
private final TextView tvCount;
|
||||
private final SelectableAdapter adapter;
|
||||
private final BulkDeleteOperation operation;
|
||||
private final Runnable onSuccess;
|
||||
private final String itemName;
|
||||
|
||||
public BulkDeleteHandler(Fragment fragment,
|
||||
View layoutBar,
|
||||
TextView tvCount,
|
||||
Button btnDelete,
|
||||
SelectableAdapter adapter,
|
||||
String itemName,
|
||||
BulkDeleteOperation operation,
|
||||
Runnable onSuccess) {
|
||||
this.fragment = fragment;
|
||||
this.layoutBar = layoutBar;
|
||||
this.tvCount = tvCount;
|
||||
this.adapter = adapter;
|
||||
this.operation = operation;
|
||||
this.onSuccess = onSuccess;
|
||||
this.itemName = itemName;
|
||||
|
||||
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI when the selection count changes.
|
||||
*/
|
||||
public void onSelectionChanged(int selectedCount) {
|
||||
if (selectedCount > 0) {
|
||||
layoutBar.setVisibility(View.VISIBLE);
|
||||
tvCount.setText(selectedCount + " selected");
|
||||
} else {
|
||||
hideBar();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the bulk delete bar and resets state.
|
||||
*/
|
||||
public void hideBar() {
|
||||
if (layoutBar != null) {
|
||||
layoutBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the confirmation dialog.
|
||||
*/
|
||||
private void confirmDelete() {
|
||||
List<Long> ids = adapter.getSelectedIds();
|
||||
if (ids.isEmpty()) return;
|
||||
|
||||
DialogUtils.showBulkDeleteConfirmDialog(fragment.requireContext(), ids.size(), () -> performDelete(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the deletion via the provided operation.
|
||||
*/
|
||||
private void performDelete(List<Long> ids) {
|
||||
operation.execute(ids).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();
|
||||
} else {
|
||||
Toast.makeText(fragment.getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,18 @@ public class DialogUtils {
|
||||
showConfirmDialog(context, "Delete " + itemName + "?", "Are you sure you want to delete this " + itemName.toLowerCase() + "? This action cannot be undone.", callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a confirmation dialog with specific "Delete" and "Cancel" buttons.
|
||||
*/
|
||||
public static void showBulkDeleteConfirmDialog(Context context, int count, DialogCallback callback) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle("Delete " + count + " item(s)?")
|
||||
.setMessage("This cannot be undone.")
|
||||
.setPositiveButton("Delete", (dialog, which) -> callback.onConfirm())
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a simple information or error dialog with an "OK" button.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.example.petstoremobile.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class to manage selection state in Adapters for bulk operations.
|
||||
*/
|
||||
public class SelectionHelper {
|
||||
|
||||
private final List<Long> selectedIds = new ArrayList<>();
|
||||
private boolean selectionMode = false;
|
||||
private final SelectionListener listener;
|
||||
|
||||
public interface SelectionListener {
|
||||
void onSelectionChanged(int count);
|
||||
void onSelectionModeToggle(boolean selectionMode);
|
||||
}
|
||||
|
||||
public SelectionHelper(SelectionListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void toggleSelection(Long id) {
|
||||
if (id == null) return;
|
||||
|
||||
if (selectedIds.contains(id)) {
|
||||
selectedIds.remove(id);
|
||||
} else {
|
||||
selectedIds.add(id);
|
||||
}
|
||||
|
||||
listener.onSelectionChanged(selectedIds.size());
|
||||
|
||||
if (selectedIds.isEmpty() && selectionMode) {
|
||||
selectionMode = false;
|
||||
listener.onSelectionModeToggle(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void startSelection(Long id) {
|
||||
selectionMode = true;
|
||||
selectedIds.add(id);
|
||||
listener.onSelectionChanged(selectedIds.size());
|
||||
listener.onSelectionModeToggle(true);
|
||||
}
|
||||
|
||||
public boolean isSelected(Long id) {
|
||||
return selectedIds.contains(id);
|
||||
}
|
||||
|
||||
public boolean isInSelectionMode() {
|
||||
return selectionMode;
|
||||
}
|
||||
|
||||
public List<Long> getSelectedIds() {
|
||||
return new ArrayList<>(selectedIds);
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
selectedIds.clear();
|
||||
selectionMode = false;
|
||||
listener.onSelectionChanged(0);
|
||||
listener.onSelectionModeToggle(false);
|
||||
}
|
||||
}
|
||||
@@ -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.PetDTO;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
@@ -57,6 +60,13 @@ public class PetViewModel extends ViewModel {
|
||||
return repository.deletePet(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple pet records.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeletePets(List<Long> ids) {
|
||||
return repository.bulkDeletePets(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads an image for a specific pet.
|
||||
*/
|
||||
|
||||
@@ -132,6 +132,37 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutBulkDelete"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@color/primary_medium"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSelectionCount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="0 selected"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBulkDelete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Delete Selected"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshPet"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -3,116 +3,133 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="@color/white">
|
||||
android:background="@color/white"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbSelectPet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:visibility="gone"
|
||||
android:clickable="false"
|
||||
android:focusable="false"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/ivPetProfile"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder"
|
||||
app:shapeAppearanceOverlay="@style/CircleImageView"
|
||||
app:strokeWidth="2dp"
|
||||
app:strokeColor="#BDBDBD"
|
||||
android:padding="2dp"
|
||||
android:contentDescription="@string/pet_profile_image_desc" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/ivPetProfile"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder"
|
||||
app:shapeAppearanceOverlay="@style/CircleImageView"
|
||||
app:strokeWidth="2dp"
|
||||
app:strokeColor="#BDBDBD"
|
||||
android:padding="2dp"
|
||||
android:contentDescription="@string/pet_profile_image_desc" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Pet Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="Status"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetName"
|
||||
android:layout_width="0dp"
|
||||
android:id="@+id/tvPetSpeciesBreed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Pet Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="Status"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetSpeciesBreed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Breed"
|
||||
android:textColor="#888888"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetPrice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="$126.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetAge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1"
|
||||
android:text="Breed"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp" />
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetPrice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="$126.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetAge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user