added helper class for bulk delete and mad pets have bulk delete

This commit is contained in:
Alex
2026-04-07 15:13:15 -06:00
parent 0813bb4b44
commit 93b4ad8c50
12 changed files with 476 additions and 185 deletions

View File

@@ -10,16 +10,16 @@ import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.databinding.ItemInventoryBinding; import com.example.petstoremobile.databinding.ItemInventoryBinding;
import com.example.petstoremobile.dtos.InventoryDTO; 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; 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 List<InventoryDTO> inventoryList;
private final OnInventoryClickListener clickListener; private final OnInventoryClickListener clickListener;
private final List<Long> selectedIds = new ArrayList<>(); private final SelectionHelper selectionHelper;
private boolean selectionMode = false;
public interface OnInventoryClickListener { public interface OnInventoryClickListener {
void onInventoryClick(int position); void onInventoryClick(int position);
@@ -30,6 +30,27 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) { public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
this.inventoryList = inventoryList; this.inventoryList = inventoryList;
this.clickListener = clickListener; 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 { public static class InventoryViewHolder extends RecyclerView.ViewHolder {
@@ -71,64 +92,31 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
} }
// Bulk delete selection mode // Bulk delete selection mode
if (selectionMode) { if (selectionHelper.isInSelectionMode()) {
binding.cbSelectInventory.setVisibility(View.VISIBLE); binding.cbSelectInventory.setVisibility(View.VISIBLE);
binding.cbSelectInventory.setChecked(inv.getInventoryId() != null binding.cbSelectInventory.setChecked(selectionHelper.isSelected(inv.getInventoryId()));
&& selectedIds.contains(inv.getInventoryId()));
} else { } else {
binding.cbSelectInventory.setVisibility(View.GONE); binding.cbSelectInventory.setVisibility(View.GONE);
binding.cbSelectInventory.setChecked(false); binding.cbSelectInventory.setChecked(false);
} }
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if (selectionMode) { if (selectionHelper.isInSelectionMode()) {
toggleSelection(inv.getInventoryId(), binding.cbSelectInventory); selectionHelper.toggleSelection(inv.getInventoryId());
notifyItemChanged(position);
} else { } else {
clickListener.onInventoryClick(holder.getAdapterPosition()); clickListener.onInventoryClick(holder.getAdapterPosition());
} }
}); });
holder.itemView.setOnLongClickListener(v -> { holder.itemView.setOnLongClickListener(v -> {
if (!selectionMode) { if (!selectionHelper.isInSelectionMode()) {
selectionMode = true; selectionHelper.startSelection(inv.getInventoryId());
toggleSelection(inv.getInventoryId(), binding.cbSelectInventory);
notifyDataSetChanged();
} }
return true; 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 @Override
public int getItemCount() { public int getItemCount() {
return inventoryList.size(); return inventoryList.size();

View File

@@ -2,6 +2,7 @@ package com.example.petstoremobile.adapters;
import android.graphics.Color; import android.graphics.Color;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -10,25 +11,41 @@ import com.example.petstoremobile.R;
import com.example.petstoremobile.api.PetApi; import com.example.petstoremobile.api.PetApi;
import com.example.petstoremobile.databinding.ItemPetBinding; import com.example.petstoremobile.databinding.ItemPetBinding;
import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.GlideUtils; import com.example.petstoremobile.utils.GlideUtils;
import com.example.petstoremobile.utils.SelectionHelper;
import java.util.List; 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 List<PetDTO> petList;
private OnPetClickListener petClickListener; private OnPetClickListener petClickListener;
private String baseUrl; private String baseUrl;
private String token; private String token;
private final SelectionHelper selectionHelper;
// Interface for pet click on recycler view // Interface for pet click on recycler view
public interface OnPetClickListener { public interface OnPetClickListener {
void onPetClick(int position); void onPetClick(int position);
void onSelectionChanged(int selectedCount);
} }
//Constructor //Constructor
public PetAdapter(List<PetDTO> petList, OnPetClickListener petClickListener) { public PetAdapter(List<PetDTO> petList, OnPetClickListener petClickListener) {
this.petList = petList; this.petList = petList;
this.petClickListener = petClickListener; 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) { public void setBaseUrl(String baseUrl) {
@@ -39,6 +56,16 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
this.token = token; 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 // Get the controls of each row in recycler view
public static class PetViewHolder extends RecyclerView.ViewHolder { public static class PetViewHolder extends RecyclerView.ViewHolder {
private final ItemPetBinding binding; private final ItemPetBinding binding;
@@ -91,8 +118,31 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
binding.ivPetProfile.setImageResource(R.drawable.placeholder); 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 //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 @Override

View File

@@ -1,5 +1,6 @@
package com.example.petstoremobile.api; package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.dtos.PetDTO;
@@ -8,6 +9,7 @@ import retrofit2.Call;
import retrofit2.http.Body; import retrofit2.http.Body;
import retrofit2.http.DELETE; import retrofit2.http.DELETE;
import retrofit2.http.GET; import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.Multipart; import retrofit2.http.Multipart;
import retrofit2.http.POST; import retrofit2.http.POST;
import retrofit2.http.PUT; import retrofit2.http.PUT;
@@ -48,6 +50,10 @@ public interface PetApi {
@DELETE("api/v1/pets/{id}") @DELETE("api/v1/pets/{id}")
Call<Void> deletePet(@Path("id") Long 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 // Upload pet image
@Multipart @Multipart
@POST("api/v1/pets/{id}/image") @POST("api/v1/pets/{id}/image")

View File

@@ -24,6 +24,7 @@ import com.example.petstoremobile.databinding.FragmentInventoryBinding;
import com.example.petstoremobile.dtos.InventoryDTO; import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.viewmodels.InventoryViewModel; import com.example.petstoremobile.viewmodels.InventoryViewModel;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; 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 List<StoreDTO> storeList = new ArrayList<>();
private InventoryAdapter adapter; private InventoryAdapter adapter;
private InventoryViewModel viewModel; private InventoryViewModel viewModel;
private BulkDeleteHandler bulkDeleteHandler;
// Pagination // Pagination
private int currentPage = 0; private int currentPage = 0;
@@ -72,6 +74,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
setupStoreFilter(); setupStoreFilter();
setupSwipeRefresh(); setupSwipeRefresh();
setupFilterToggle(); setupFilterToggle();
setupBulkDelete();
loadInventory(true); loadInventory(true);
loadStoreData(); loadStoreData();
@@ -87,11 +90,25 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
} }
}); });
binding.btnBulkDelete.setOnClickListener(v -> confirmBulkDelete());
return binding.getRoot(); 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 @Override
public void onDestroyView() { public void onDestroyView() {
super.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. * 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 @Override
public void onSelectionChanged(int selectedCount) { public void onSelectionChanged(int selectedCount) {
if (selectedCount > 0) { if (bulkDeleteHandler != null) {
binding.btnBulkDelete.setVisibility(View.VISIBLE); bulkDeleteHandler.onSelectionChanged(selectedCount);
binding.tvSelectionCount.setVisibility(View.VISIBLE);
binding.tvSelectionCount.setText(selectedCount + " selected");
} else {
hideBulkDeleteBar();
} }
} }
} }

View File

@@ -25,6 +25,7 @@ import com.example.petstoremobile.databinding.FragmentPetBinding;
import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.viewmodels.PetViewModel; import com.example.petstoremobile.viewmodels.PetViewModel;
@@ -46,6 +47,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
private PetAdapter adapter; private PetAdapter adapter;
private PetViewModel viewModel; private PetViewModel viewModel;
private StoreViewModel storeViewModel; private StoreViewModel storeViewModel;
private BulkDeleteHandler bulkDeleteHandler;
@Inject @Named("baseUrl") String baseUrl; @Inject @Named("baseUrl") String baseUrl;
@@ -74,6 +76,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
setupStoreFilter(); setupStoreFilter();
setupSwipeRefresh(); setupSwipeRefresh();
setupFilterToggle(); setupFilterToggle();
setupBulkDelete();
binding.fabAddPet.setOnClickListener(v -> openPetDetails()); binding.fabAddPet.setOnClickListener(v -> openPetDetails());
@@ -90,6 +93,22 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
return binding.getRoot(); 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 @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
@@ -231,6 +250,13 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
openPetProfile(position); openPetProfile(position);
} }
@Override
public void onSelectionChanged(int selectedCount) {
if (bulkDeleteHandler != null) {
bulkDeleteHandler.onSelectionChanged(selectedCount);
}
}
/** /**
* Fetches pet data from the server with all active filters. * Fetches pet data from the server with all active filters.
*/ */

View File

@@ -3,6 +3,7 @@ package com.example.petstoremobile.repositories;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.PetApi; import com.example.petstoremobile.api.PetApi;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
@@ -57,6 +58,13 @@ public class PetRepository extends BaseRepository {
return executeCall(petApi.deletePet(id)); 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. * Uploads an image file for a specific pet via the API.
*/ */

View File

@@ -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();
}
}
});
}
}

View File

@@ -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); 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. * Shows a simple information or error dialog with an "OK" button.
*/ */

View File

@@ -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);
}
}

View File

@@ -3,11 +3,14 @@ package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.repositories.PetRepository; import com.example.petstoremobile.repositories.PetRepository;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel; import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -57,6 +60,13 @@ public class PetViewModel extends ViewModel {
return repository.deletePet(id); 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. * Uploads an image for a specific pet.
*/ */

View File

@@ -132,6 +132,37 @@
</LinearLayout> </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 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshPet" android:id="@+id/swipeRefreshPet"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -3,12 +3,27 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="horizontal"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingBottom="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="vertical">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -115,4 +130,6 @@
android:background="#F0F0F0" android:background="#F0F0F0"
android:layout_marginTop="12dp"/> android:layout_marginTop="12dp"/>
</LinearLayout>
</LinearLayout> </LinearLayout>