From 003c7ec58a56b7555b5dd6921b5dc2c344bad3cf Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:23:58 -0600 Subject: [PATCH] changed petDetailFragment to support new backend --- .../petstoremobile/dtos/CustomerDTO.java | 29 +++- .../detailfragments/PetDetailFragment.java | 154 +++++++++++++++++- .../PetProfileFragment.java | 7 + .../petstoremobile/utils/InputValidator.java | 20 +++ .../petstoremobile/utils/SpinnerUtils.java | 4 +- .../main/res/layout/fragment_pet_detail.xml | 28 ++++ .../main/res/layout/fragment_pet_profile.xml | 31 +++- 7 files changed, 269 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/CustomerDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/CustomerDTO.java index 178b0033..21376a63 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/CustomerDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/CustomerDTO.java @@ -1,6 +1,9 @@ package com.example.petstoremobile.dtos; +import com.google.gson.annotations.SerializedName; + public class CustomerDTO { + @SerializedName("id") private Long customerId; private String firstName; private String lastName; @@ -12,18 +15,34 @@ public class CustomerDTO { return customerId; } + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + public String getFirstName() { return firstName; } + public void setFirstName(String firstName) { + this.firstName = firstName; + } + public String getLastName() { return lastName; } + public void setLastName(String lastName) { + this.lastName = lastName; + } + public String getEmail() { return email; } + public void setEmail(String email) { + this.email = email; + } + public String getFullName() { return firstName + " " + lastName; } @@ -32,7 +51,15 @@ public class CustomerDTO { return createdAt; } + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + public String getUpdatedAt() { return updatedAt; } -} \ No newline at end of file + + public void setUpdatedAt(String updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java index 57c0ac18..62d8bf42 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java @@ -11,18 +11,27 @@ import androidx.navigation.fragment.NavHostFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; import com.example.petstoremobile.R; import com.example.petstoremobile.databinding.FragmentPetDetailBinding; +import com.example.petstoremobile.dtos.CustomerDTO; import com.example.petstoremobile.dtos.PetDTO; +import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.utils.ActivityLogger; import com.example.petstoremobile.utils.DialogUtils; import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.SpinnerUtils; +import com.example.petstoremobile.viewmodels.CustomerViewModel; import com.example.petstoremobile.viewmodels.PetViewModel; +import com.example.petstoremobile.viewmodels.StoreViewModel; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import dagger.hilt.android.AndroidEntryPoint; @@ -38,11 +47,19 @@ public class PetDetailFragment extends Fragment { private boolean isEditing = false; private PetViewModel viewModel; + private CustomerViewModel customerViewModel; + private StoreViewModel storeViewModel; + private List customerList = new ArrayList<>(); + private List storeList = new ArrayList<>(); + private Long selectedCustomerId = null; + private Long selectedStoreId = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(PetViewModel.class); + customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class); + storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class); } @Override @@ -57,6 +74,8 @@ public class PetDetailFragment extends Fragment { super.onViewCreated(view, savedInstanceState); setupSpinner(); + loadCustomers(); + loadStores(); handleArguments(); //set button click listeners @@ -90,6 +109,36 @@ public class PetDetailFragment extends Fragment { double price = Double.parseDouble(binding.etPetPrice.getText().toString().trim()); String status = binding.spinnerPetStatus.getSelectedItem().toString(); + // Get selected customer + Long customerId = null; + int customerPos = binding.spinnerCustomer.getSelectedItemPosition(); + if (customerPos > 0) { // 0 means no customer for pet + customerId = customerList.get(customerPos - 1).getCustomerId(); + } + + // Get selected store + Long storeId = null; + int storePos = binding.spinnerStore.getSelectedItemPosition(); + if (storePos > 0) { + storeId = storeList.get(storePos - 1).getStoreId(); + } + + // Validation: If status is Available, a store must be selected + if ("Available".equalsIgnoreCase(status)) { + if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return; + } + + // Validation: If status is Owned, an owner must be selected + if ("Owned".equalsIgnoreCase(status)) { + if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return; + } + + // Validation: If status is Adopted, an owner and store must be selected + if ("Adopted".equalsIgnoreCase(status)) { + if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return; + if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return; + } + //create a pet object to send to the API PetDTO petDTO = new PetDTO(); petDTO.setPetName(name); @@ -98,6 +147,8 @@ public class PetDetailFragment extends Fragment { petDTO.setPetAge(age); petDTO.setPetPrice(price); petDTO.setPetStatus(status); + petDTO.setCustomerId(customerId); + petDTO.setStoreId(storeId); //check if the pet is being edited or added if (isEditing) { @@ -197,17 +248,118 @@ public class PetDetailFragment extends Fragment { binding.etPetPrice.setText(String.format(Locale.getDefault(), "%.2f", p.getPetPrice())); } SpinnerUtils.setSelectionByValue(binding.spinnerPetStatus, p.getPetStatus()); + + selectedCustomerId = p.getCustomerId(); + updateCustomerSpinnerSelection(); + + selectedStoreId = p.getStoreId(); + updateStoreSpinnerSelection(); } else if (resource.status == Resource.Status.ERROR) { Toast.makeText(getContext(), "Failed to load pet: " + resource.message, Toast.LENGTH_SHORT).show(); } }); } + /** + * Fetches the list of customers and populates the spinner. + */ + private void loadCustomers() { + customerViewModel.getAllCustomers(0, 1000).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + customerList = resource.data.getContent(); + updateCustomerSpinnerSelection(); + } + }); + } + + /** + * Fetches the list of stores and populates the spinner. + */ + private void loadStores() { + storeViewModel.getAllStores(0, 1000).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + storeList = resource.data.getContent(); + updateStoreSpinnerSelection(); + } + }); + } + + /** + * Updates the customer spinner with the current list and sets the selection if needed. + */ + private void updateCustomerSpinnerSelection() { + SpinnerUtils.populateSpinner( + requireContext(), + binding.spinnerCustomer, + customerList, + CustomerDTO::getFullName, + "No Owner", + selectedCustomerId, + CustomerDTO::getCustomerId + ); + } + + /** + * Updates the store spinner with the current list and sets the selection if needed. + */ + private void updateStoreSpinnerSelection() { + SpinnerUtils.populateSpinner( + requireContext(), + binding.spinnerStore, + storeList, + StoreDTO::getStoreName, + "None", + selectedStoreId, + StoreDTO::getStoreId + ); + } + /** * Initializes the spinner for pet status selection. */ private void setupSpinner() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, - new String[]{"Available", "Adopted"}); + new String[]{"Available", "Adopted", "Owned"}); + + binding.spinnerPetStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String status = parent.getItemAtPosition(position).toString(); + + // Clear any existing error icons when status changes + clearSpinnerError(binding.spinnerCustomer); + clearSpinnerError(binding.spinnerStore); + + //Disable the customer spinner if the status is "Available" + if ("Available".equalsIgnoreCase(status)) { + binding.spinnerCustomer.setSelection(0); + binding.spinnerCustomer.setEnabled(false); + } else { + binding.spinnerCustomer.setEnabled(true); + } + + //Disable the store spinner if the status is "Owned" + if ("Owned".equalsIgnoreCase(status)) { + binding.spinnerStore.setSelection(0); + binding.spinnerStore.setEnabled(false); + } else { + binding.spinnerStore.setEnabled(true); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + } + + /** + * Clears error messages from a Spinner's selected view. + */ + private void clearSpinnerError(Spinner spinner) { + View selectedView = spinner.getSelectedView(); + if (selectedView instanceof TextView) { + ((TextView) selectedView).setError(null); + } } } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java index 68fb3eec..9e876c69 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/listprofilefragments/PetProfileFragment.java @@ -130,6 +130,13 @@ public class PetProfileFragment extends Fragment { } else { binding.tvPetPrice.setText("$0.00"); } + + // Display owner name if available, otherwise show No Owner + if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) { + binding.tvPetOwner.setText(pet.getCustomerName()); + } else { + binding.tvPetOwner.setText("No Owner"); + } } else if (resource.status == Resource.Status.ERROR) { Toast.makeText(getContext(), "Failed to load pet data: " + resource.message, Toast.LENGTH_SHORT).show(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/InputValidator.java b/android/app/src/main/java/com/example/petstoremobile/utils/InputValidator.java index d173df14..a10389b6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/InputValidator.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/InputValidator.java @@ -1,6 +1,9 @@ package com.example.petstoremobile.utils; +import android.view.View; import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; public class InputValidator { @@ -94,4 +97,21 @@ public class InputValidator { } return true; } + + /** + * Checks if a selection has been made in a Spinner. + * Assumes position 0 is a placeholder like "None" or "Select". + */ + public static boolean isSpinnerSelected(Spinner spinner, String fieldName) { + if (spinner.getSelectedItemPosition() <= 0) { + View selectedView = spinner.getSelectedView(); + if (selectedView instanceof TextView) { + TextView tv = (TextView) selectedView; + tv.setError(fieldName + " is required"); + spinner.requestFocus(); + } + return false; + } + return true; + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java b/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java index 5095199e..fa22d21d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/SpinnerUtils.java @@ -8,6 +8,7 @@ import com.example.petstoremobile.adapters.BlackTextArrayAdapter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Function; /** @@ -36,7 +37,8 @@ public class SpinnerUtils { if (preselectedId != null && preselectedId != -1) { int offset = (defaultText != null) ? 1 : 0; for (int i = 0; i < data.size(); i++) { - if (idExtractor.apply(data.get(i)).equals(preselectedId)) { + Long currentId = idExtractor.apply(data.get(i)); + if (Objects.equals(currentId, preselectedId)) { spinner.setSelection(i + offset); break; } diff --git a/android/app/src/main/res/layout/fragment_pet_detail.xml b/android/app/src/main/res/layout/fragment_pet_detail.xml index 48558349..917a5de6 100644 --- a/android/app/src/main/res/layout/fragment_pet_detail.xml +++ b/android/app/src/main/res/layout/fragment_pet_detail.xml @@ -161,6 +161,34 @@ + + + + + + + + diff --git a/android/app/src/main/res/layout/fragment_pet_profile.xml b/android/app/src/main/res/layout/fragment_pet_profile.xml index b750bd29..f5af4445 100644 --- a/android/app/src/main/res/layout/fragment_pet_profile.xml +++ b/android/app/src/main/res/layout/fragment_pet_profile.xml @@ -221,6 +221,35 @@ + + + + + + + + @@ -241,4 +270,4 @@ - \ No newline at end of file +