From b3ff789f1bd96fa716badf5f431b3c9bd4dac2a0 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 7 Apr 2026 22:17:15 -0600 Subject: [PATCH] revert adoption fragment --- .../listfragments/AdoptionFragment.java | 373 +++++++++++++----- 1 file changed, 284 insertions(+), 89 deletions(-) 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 450b7dc8..f4757314 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 @@ -1,142 +1,337 @@ package com.example.petstoremobile.fragments.listfragments; +import android.graphics.Color; import android.os.Bundle; -import android.text.*; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Log; -import android.view.*; -import android.widget.*; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.AdoptionAdapter; -import com.example.petstoremobile.api.AdoptionApi; -import com.example.petstoremobile.api.RetrofitClient; +import com.example.petstoremobile.databinding.FragmentAdoptionBinding; import com.example.petstoremobile.dtos.AdoptionDTO; -import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.fragments.ListFragment; -import com.example.petstoremobile.fragments.listfragments.detailfragments.AdoptionDetailFragment; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import java.util.*; -import retrofit2.*; +import com.example.petstoremobile.utils.BulkDeleteHandler; +import com.example.petstoremobile.utils.Resource; +import com.example.petstoremobile.utils.SpinnerUtils; +import com.example.petstoremobile.viewmodels.AdoptionViewModel; +import com.example.petstoremobile.utils.EventDecorator; +import com.example.petstoremobile.viewmodels.StoreViewModel; +import com.prolificinteractive.materialcalendarview.CalendarDay; +import com.prolificinteractive.materialcalendarview.CalendarMode; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; + +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener { + private FragmentAdoptionBinding binding; private List adoptionList = new ArrayList<>(); - private List filteredList = new ArrayList<>(); + private List storeList = new ArrayList<>(); private AdoptionAdapter adapter; - private AdoptionApi api; - private SwipeRefreshLayout swipeRefresh; - private EditText etSearch; - private ImageButton hamburger; + private AdoptionViewModel adoptionViewModel; + private StoreViewModel storeViewModel; + private BulkDeleteHandler bulkDeleteHandler; + private CalendarDay selectedCalendarDay; + private boolean isMonthMode = false; + private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + + /** + * Initializes the fragment and its ViewModels. + */ + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + adoptionViewModel = new ViewModelProvider(this).get(AdoptionViewModel.class); + storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class); + } + + /** + * Sets up the fragment's UI components, including RecyclerView, Search, SwipeRefresh, and Calendar. + */ + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentAdoptionBinding.inflate(inflater, container, false); + + setupRecyclerView(); + setupSearch(); + setupStatusFilter(); + setupStoreFilter(); + setupSwipeRefresh(); + setupCalendar(); + setupFilterToggle(); + setupBulkDelete(); + + binding.fabAddAdoption.setOnClickListener(v -> openDetail(-1)); + + binding.btnHamburgerAdoption.setOnClickListener(v -> { + Fragment parent = getParentFragment(); + if (parent != null) { + Fragment grandParent = parent.getParentFragment(); + if (grandParent instanceof ListFragment) { + ((ListFragment) grandParent).openDrawer(); + } + } + }); + + binding.btnToggleCalendarModeAdoption.setOnClickListener(v -> toggleCalendarMode()); + + return binding.getRoot(); + } + + private void setupBulkDelete() { + bulkDeleteHandler = new BulkDeleteHandler( + this, + binding.layoutBulkDelete, + binding.tvSelectionCount, + binding.btnBulkDelete, + adapter, + "adoption", + adoptionViewModel::bulkDeleteAdoptions, + this::loadAdoptions + ); + } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_adoption, container, false); + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } - api = RetrofitClient.getAdoptionApi(requireContext()); - hamburger = view.findViewById(R.id.btnHamburgerAdoption); - - setupRecyclerView(view); - setupSearch(view); - setupSwipeRefresh(view); + @Override + public void onResume() { + super.onResume(); loadAdoptions(); - - FloatingActionButton fab = view.findViewById(R.id.fabAddAdoption); - fab.setOnClickListener(v -> openDetail(-1)); - - hamburger.setOnClickListener(v -> { - ListFragment lf = (ListFragment) getParentFragment(); - if (lf != null) lf.openDrawer(); - }); - - return view; + loadStoreData(); } - private void setupRecyclerView(View view) { - RecyclerView rv = view.findViewById(R.id.recyclerViewAdoptions); - adapter = new AdoptionAdapter(filteredList, this); - rv.setLayoutManager(new LinearLayoutManager(getContext())); - rv.setAdapter(adapter); + /** + * Toggles the calendar display between week and month modes. + */ + private void toggleCalendarMode() { + isMonthMode = !isMonthMode; + binding.calendarViewAdoption.state().edit() + .setCalendarDisplayMode(isMonthMode ? CalendarMode.MONTHS : CalendarMode.WEEKS) + .commit(); } - private void setupSearch(View view) { - etSearch = view.findViewById(R.id.etSearchAdoption); - etSearch.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int a, int b, int c) {} - public void afterTextChanged(Editable s) {} - public void onTextChanged(CharSequence s, int a, int b, int c) { - filter(s.toString()); + /** + * Sets up the filter toggle button to show/hide the filter layout. + */ + private void setupFilterToggle() { + binding.btnToggleFilterAdoption.setOnClickListener(v -> { + if (binding.layoutFilterAdoption.getVisibility() == View.GONE) { + binding.layoutFilterAdoption.setVisibility(View.VISIBLE); + binding.btnToggleFilterAdoption.setImageResource(android.R.drawable.ic_menu_close_clear_cancel); + } else { + binding.layoutFilterAdoption.setVisibility(View.GONE); + binding.btnToggleFilterAdoption.setImageResource(android.R.drawable.ic_menu_search); + + // Reset filters when closing + binding.etSearchAdoption.setText(""); + binding.spinnerStatusAdoption.setSelection(0); + binding.spinnerStoreAdoption.setSelection(0); + selectedCalendarDay = null; + binding.calendarViewAdoption.clearSelection(); } }); } - private void setupSwipeRefresh(View view) { - swipeRefresh = view.findViewById(R.id.swipeRefreshAdoption); - swipeRefresh.setOnRefreshListener(this::loadAdoptions); + /** + * Sets up the date selection listener for the calendar. + */ + private void setupCalendar() { + binding.calendarViewAdoption.setOnDateChangedListener((widget, date, selected) -> { + if (selected) { + if (date.equals(selectedCalendarDay)) { + selectedCalendarDay = null; + binding.calendarViewAdoption.clearSelection(); + } else { + selectedCalendarDay = date; + } + } else { + selectedCalendarDay = null; + } + loadAdoptions(); + }); } - private void filter(String query) { - filteredList.clear(); - if (query.isEmpty()) { - filteredList.addAll(adoptionList); - } else { - String lower = query.toLowerCase(); - for (AdoptionDTO a : adoptionList) { - if ((a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lower)) - || (a.getPetName() != null && a.getPetName().toLowerCase().contains(lower)) - || (a.getAdoptionStatus() != null && a.getAdoptionStatus().toLowerCase().contains(lower))) { - filteredList.add(a); + /** + * Updates the calendar decorators to highlight days with adoptions. + */ + private void updateCalendarDecorators() { + HashSet datesWithAdoptions = new HashSet<>(); + for (AdoptionDTO adoption : adoptionList) { + try { + if (adoption.getAdoptionDate() != null) { + Date date = dateFormat.parse(adoption.getAdoptionDate()); + if (date != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + datesWithAdoptions.add(CalendarDay.from(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH))); + } } + } catch (ParseException e) { + Log.e("AdoptionFragment", "Error parsing date: " + adoption.getAdoptionDate()); } } - adapter.notifyDataSetChanged(); + binding.calendarViewAdoption.removeDecorators(); + binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions)); } - private void loadAdoptions() { - if (swipeRefresh != null) swipeRefresh.setRefreshing(true); - api.getAllAdoptions(0, 100, null, null, null, null, null).enqueue(new Callback>() { - public void onResponse(Call> c, - Response> r) { - if (swipeRefresh != null) swipeRefresh.setRefreshing(false); - if (r.isSuccessful() && r.body() != null) { - adoptionList.clear(); - adoptionList.addAll(r.body().getContent()); - filter(etSearch != null ? etSearch.getText().toString() : ""); - } else { - Toast.makeText(getContext(), "Failed to load adoptions", Toast.LENGTH_SHORT).show(); - Log.e("AdoptionFragment", "Error: " + r.message()); - } + /** + * Initializes the RecyclerView for displaying adoptions. + */ + private void setupRecyclerView() { + adapter = new AdoptionAdapter(adoptionList, this); + binding.recyclerViewAdoptions.setLayoutManager(new LinearLayoutManager(getContext())); + binding.recyclerViewAdoptions.setAdapter(adapter); + } + + /** + * Sets up the search bar for filtering + */ + private void setupSearch() { + binding.etSearchAdoption.addTextChangedListener(new TextWatcher() { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + loadAdoptions(); } - public void onFailure(Call> c, Throwable t) { - if (swipeRefresh != null) swipeRefresh.setRefreshing(false); - Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); - Log.e("AdoptionFragment", t.getMessage()); + @Override public void afterTextChanged(Editable s) {} + }); + } + + /** + * Configures the status filter spinner. + */ + private void setupStatusFilter() { + String[] statuses = {"All Statuses", "Completed", "Pending", "Cancelled"}; + SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, this::loadAdoptions); + } + + /** + * Configures the store filter spinner. + */ + private void setupStoreFilter() { + SpinnerUtils.setupFilterSpinner(binding.spinnerStoreAdoption, this::loadAdoptions); + } + + /** + * Fetches store data to populate the store filter. + */ + private void loadStoreData() { + storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + storeList = resource.data.getContent(); + SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStoreAdoption, storeList, + StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId); } }); } + /** + * Sets up the SwipeRefreshLayout to reload adoption data. + */ + private void setupSwipeRefresh() { + binding.swipeRefreshAdoption.setOnRefreshListener(this::loadAdoptions); + } + + /** + * Fetches the adoption list from the server through the ViewModel. + */ + private void loadAdoptions() { + String query = binding.etSearchAdoption.getText().toString().trim(); + String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses"; + + Long storeId = null; + if (binding.spinnerStoreAdoption.getSelectedItemPosition() > 0 && !storeList.isEmpty()) { + storeId = storeList.get(binding.spinnerStoreAdoption.getSelectedItemPosition() - 1).getStoreId(); + } + + String selectedDateString = null; + if (selectedCalendarDay != null) { + selectedDateString = String.format(Locale.getDefault(), "%04d-%02d-%02d", + selectedCalendarDay.getYear(), selectedCalendarDay.getMonth(), selectedCalendarDay.getDay()); + } + + if (status.equals("All Statuses")) status = null; + else status = status.toUpperCase(); + + adoptionViewModel.getAllAdoptions(0, 500, query, status, storeId, selectedDateString, null).observe(getViewLifecycleOwner(), resource -> { + if (resource == null) return; + + // Check the status to see if the resource is loaded and display the data + switch (resource.status) { + case LOADING: + // Show loading indicator + binding.swipeRefreshAdoption.setRefreshing(true); + break; + case SUCCESS: + // Hide loading indicator and display data + binding.swipeRefreshAdoption.setRefreshing(false); + if (resource.data != null) { + adoptionList.clear(); + adoptionList.addAll(resource.data.getContent()); + updateCalendarDecorators(); + adapter.notifyDataSetChanged(); + } + break; + case ERROR: + // Hide loading indicator and toast error message + binding.swipeRefreshAdoption.setRefreshing(false); + Toast.makeText(getContext(), "Failed to load adoptions: " + resource.message, Toast.LENGTH_SHORT).show(); + Log.e("AdoptionFragment", "Error loading adoptions: " + resource.message); + break; + } + }); + } + + /** + * Navigates to the adoption detail screen for a specific adoption or to create a new one. + */ private void openDetail(int position) { Bundle args = new Bundle(); if (position != -1) { - AdoptionDTO a = filteredList.get(position); + AdoptionDTO a = adoptionList.get(position); args.putLong("adoptionId", a.getAdoptionId()); - args.putLong("petId", a.getPetId() != null ? a.getPetId() : -1); - args.putLong("customerId", a.getCustomerId() != null ? a.getCustomerId() : -1); - args.putString("adoptionDate", a.getAdoptionDate()); - args.putString("adoptionStatus", a.getAdoptionStatus()); - if (a.getEmployeeId() != null) - args.putLong("employeeId", a.getEmployeeId());} + } + NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args); } + /** + * Handles item click in the adoption list. + */ @Override public void onAdoptionClick(int position) { openDetail(position); } @Override - public void onSelectionChanged(int count) {} + public void onSelectionChanged(int selectedCount) { + if (bulkDeleteHandler != null) { + bulkDeleteHandler.onSelectionChanged(selectedCount); + } + } }