changed backend so can sortBy productName and added search to productSupplier

This commit is contained in:
Alex
2026-04-07 05:48:24 -06:00
parent cbd038d8fb
commit 5c9d04fc88
9 changed files with 270 additions and 66 deletions

View File

@@ -10,7 +10,11 @@ public interface ProductSupplierApi {
@GET("api/v1/product-suppliers")
Call<PageResponse<ProductSupplierDTO>> getAllProductSuppliers(
@Query("page") int page,
@Query("size") int size);
@Query("size") int size,
@Query("q") String query,
@Query("productId") Long productId,
@Query("supplierId") Long supplierId,
@Query("sort") String sort);
@GET("api/v1/product-suppliers/{productId}/{supplierId}")
Call<ProductSupplierDTO> getProductSupplierById(

View File

@@ -144,7 +144,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
* Configures the status filter spinner.
*/
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Available", "Adopted"};
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned"};
WhiteTextArrayAdapter<String> adapter = new WhiteTextArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statuses);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerStatus.setAdapter(adapter);

View File

@@ -1,10 +1,15 @@
package com.example.petstoremobile.fragments.listfragments;
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.AdapterView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@@ -15,11 +20,18 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.ProductSupplierAdapter;
import com.example.petstoremobile.databinding.FragmentProductSupplierBinding;
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.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
import com.example.petstoremobile.viewmodels.ProductViewModel;
import com.example.petstoremobile.viewmodels.SupplierViewModel;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import dagger.hilt.android.AndroidEntryPoint;
@@ -29,17 +41,23 @@ public class ProductSupplierFragment extends Fragment
private FragmentProductSupplierBinding binding;
private List<ProductSupplierDTO> psList = new ArrayList<>();
private List<ProductSupplierDTO> filteredList = new ArrayList<>();
private List<ProductDTO> productList = new ArrayList<>();
private List<SupplierDTO> supplierList = new ArrayList<>();
private ProductSupplierAdapter adapter;
private ProductSupplierViewModel viewModel;
private ProductViewModel productViewModel;
private SupplierViewModel supplierViewModel;
/**
* Initializes the fragment and its associated ProductSupplierViewModel.
* Initializes the fragment and its associated ViewModels.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class);
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
supplierViewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
}
/**
@@ -52,8 +70,10 @@ public class ProductSupplierFragment extends Fragment
setupRecyclerView();
setupSearch();
setupProductFilter();
setupSupplierFilter();
setupSwipeRefresh();
loadData();
setupFilterToggle();
binding.fabAddPS.setOnClickListener(v -> openDetail(-1));
@@ -76,11 +96,41 @@ public class ProductSupplierFragment extends Fragment
binding = null;
}
/**
* Reloads data every time the fragment becomes visible.
*/
@Override
public void onResume() {
super.onResume();
loadData();
loadFilterData();
}
/**
* Sets up the filter toggle button to show/hide the filter layout.
*/
private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> {
if (binding.layoutFilter.getVisibility() == View.GONE) {
binding.layoutFilter.setVisibility(View.VISIBLE);
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
} else {
binding.layoutFilter.setVisibility(View.GONE);
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
// Reset filters when closing
binding.etSearchPS.setText("");
binding.spinnerProduct.setSelection(0);
binding.spinnerSupplier.setSelection(0);
}
});
}
/**
* Initializes the RecyclerView with a layout manager and adapter for product-supplier data.
*/
private void setupRecyclerView() {
adapter = new ProductSupplierAdapter(filteredList, this);
adapter = new ProductSupplierAdapter(psList, this);
binding.recyclerViewPS.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerViewPS.setAdapter(adapter);
}
@@ -90,10 +140,57 @@ public class ProductSupplierFragment extends Fragment
*/
private void setupSearch() {
binding.etSearchPS.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());
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
loadData();
}
@Override public void afterTextChanged(Editable s) {}
});
}
/**
* Configures the product filter spinner.
*/
private void setupProductFilter() {
binding.spinnerProduct.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
}
/**
* Configures the supplier filter spinner.
*/
private void setupSupplierFilter() {
binding.spinnerSupplier.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
}
/**
* Fetches products and suppliers to populate the filters.
*/
private void loadFilterData() {
productViewModel.getAllProducts(null, null, 0, 100, "prodName").observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
productList = resource.data.getContent();
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerProduct, productList,
ProductDTO::getProdName, "All Products", -1L, ProductDTO::getProdId);
}
});
supplierViewModel.getAllSuppliers(0, 100, null, "supCompany").observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
supplierList = resource.data.getContent();
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerSupplier, supplierList,
SupplierDTO::getSupCompany, "All Suppliers", -1L, SupplierDTO::getSupId);
}
});
}
@@ -106,30 +203,23 @@ public class ProductSupplierFragment extends Fragment
}
/**
* Filters the product-supplier list based on the search query.
*/
private void filter(String query) {
filteredList.clear();
if (query.isEmpty()) {
filteredList.addAll(psList);
} else {
String lower = query.toLowerCase();
for (ProductSupplierDTO ps : psList) {
if ((ps.getProductName() != null && ps.getProductName().toLowerCase().contains(lower))
|| (ps.getSupplierName() != null && ps.getSupplierName().toLowerCase().contains(lower))) {
filteredList.add(ps);
}
}
}
adapter.notifyDataSetChanged();
}
/**
* Fetches all product-supplier data from the server through the ViewModel.
* Fetches product-supplier data from the server through the ViewModel with search query and filters.
*/
private void loadData() {
//Load all product suppliers from the backend using viewModel
viewModel.getAllProductSuppliers(0, 100).observe(getViewLifecycleOwner(), resource -> {
String query = binding.etSearchPS.getText().toString().trim();
if (query.isEmpty()) query = null;
Long productId = null;
if (binding.spinnerProduct.getSelectedItemPosition() > 0 && !productList.isEmpty()) {
productId = productList.get(binding.spinnerProduct.getSelectedItemPosition() - 1).getProdId();
}
Long supplierId = null;
if (binding.spinnerSupplier.getSelectedItemPosition() > 0 && !supplierList.isEmpty()) {
supplierId = supplierList.get(binding.spinnerSupplier.getSelectedItemPosition() - 1).getSupId();
}
viewModel.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName").observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
// Check the status to see if the resource is loaded and display the data
@@ -144,7 +234,7 @@ public class ProductSupplierFragment extends Fragment
if (resource.data != null) {
psList.clear();
psList.addAll(resource.data.getContent());
filter(binding.etSearchPS != null ? binding.etSearchPS.getText().toString() : "");
adapter.notifyDataSetChanged();
}
break;
case ERROR:
@@ -163,7 +253,7 @@ public class ProductSupplierFragment extends Fragment
private void openDetail(int position) {
Bundle args = new Bundle();
if (position != -1) {
ProductSupplierDTO ps = filteredList.get(position);
ProductSupplierDTO ps = psList.get(position);
args.putLong("productId", ps.getProductId());
args.putLong("supplierId", ps.getSupplierId());
}

View File

@@ -23,8 +23,8 @@ public class ProductSupplierRepository extends BaseRepository {
/**
* Retrieves a paginated list of all product-supplier relationships from the API.
*/
public LiveData<Resource<PageResponse<ProductSupplierDTO>>> getAllProductSuppliers(int page, int size) {
return executeCall(api.getAllProductSuppliers(page, size));
public LiveData<Resource<PageResponse<ProductSupplierDTO>>> getAllProductSuppliers(int page, int size, String query, Long productId, Long supplierId, String sort) {
return executeCall(api.getAllProductSuppliers(page, size, query, productId, supplierId, sort));
}
/**
@@ -54,4 +54,4 @@ public class ProductSupplierRepository extends BaseRepository {
public LiveData<Resource<Void>> deleteProductSupplier(Long productId, Long supplierId) {
return executeCall(api.deleteProductSupplier(productId, supplierId));
}
}
}

View File

@@ -24,8 +24,8 @@ public class ProductSupplierViewModel extends ViewModel {
/**
* Fetches a paginated list of all product-supplier relationships.
*/
public LiveData<Resource<PageResponse<ProductSupplierDTO>>> getAllProductSuppliers(int page, int size) {
return repository.getAllProductSuppliers(page, size);
public LiveData<Resource<PageResponse<ProductSupplierDTO>>> getAllProductSuppliers(int page, int size, String query, Long productId, Long supplierId, String sort) {
return repository.getAllProductSuppliers(page, size, query, productId, supplierId, sort);
}
/**

View File

@@ -28,27 +28,100 @@
android:contentDescription="Open menu"/>
<TextView
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Product Suppliers"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold"/>
android:textStyle="bold"
android:layout_marginStart="8dp"/>
<ImageButton
android:id="@+id/btnToggleFilter"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@android:drawable/ic_menu_search"
android:background="?attr/selectableItemBackgroundBorderless"
app:tint="@color/white"
android:contentDescription="Toggle filter"/>
</LinearLayout>
<EditText
android:id="@+id/etSearchPS"
<LinearLayout
android:id="@+id/layoutFilter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:hint="Search by product or supplier..."
android:inputType="text"
android:drawableStart="@android:drawable/ic_menu_search"
android:drawablePadding="8dp"
android:background="@android:color/white"
android:padding="12dp"
android:textColor="@color/text_dark"/>
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:visibility="gone"
android:background="@color/primary_dark"
android:elevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@drawable/bg_search_bar"
android:gravity="center_vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@android:drawable/ic_menu_search"
android:alpha="0.6"
android:contentDescription="Search icon"/>
<EditText
android:id="@+id/etSearchPS"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Search by product or supplier..."
android:inputType="text"
android:background="@android:color/transparent"
android:textColor="@color/text_dark"
android:textColorHint="#99000000"
android:textSize="14sp"
android:paddingStart="8dp"
android:paddingEnd="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<Spinner
android:id="@+id/spinnerProduct"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:background="@drawable/bg_spinner"
android:paddingStart="12dp"
android:paddingEnd="8dp"/>
<View
android:layout_width="8dp"
android:layout_height="0dp"/>
<Spinner
android:id="@+id/spinnerSupplier"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:background="@drawable/bg_spinner"
android:paddingStart="12dp"
android:paddingEnd="8dp"/>
</LinearLayout>
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshPS"