From 6ece516cc636645c476460ffef493b526a534f33 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Thu, 9 Apr 2026 23:39:34 -0600 Subject: [PATCH] Fixed profile issue with camera and added viewstate to pet and service --- .../listfragments/ServiceFragment.java | 7 +- .../detailfragments/PetDetailFragment.java | 147 ++++++++------ .../ServiceDetailFragment.java | 60 ++++-- .../petstoremobile/utils/FileUtils.java | 24 ++- .../utils/ImagePickerHelper.java | 15 +- .../viewmodels/PetDetailViewModel.java | 189 +++++++++++++++--- .../viewmodels/ServiceDetailViewModel.java | 81 +++++++- 7 files changed, 398 insertions(+), 125 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java index 3a1b45a1..1aaf625d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ServiceFragment.java @@ -56,11 +56,14 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic setupFilterToggle(); setupBulkDelete(); observeViewModel(); - + loadServices(true); UIUtils.setupHamburgerMenu(binding.btnHamburger, this); + binding.fabAddService.setOnClickListener(v -> + NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail)); + return binding.getRoot(); } @@ -156,4 +159,4 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic bulkDeleteHandler.onSelectionChanged(count); } } -} +} \ No newline at end of file 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 ee1b34fc..d56ba6a6 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 @@ -41,9 +41,7 @@ public class PetDetailFragment extends Fragment { private FragmentPetDetailBinding binding; private PetDetailViewModel viewModel; - - private Long selectedCustomerId = null; - private Long selectedStoreId = null; + private boolean isUpdatingUI = false; @Override public void onCreate(Bundle savedInstanceState) { @@ -65,6 +63,7 @@ public class PetDetailFragment extends Fragment { setupSpinner(); observeViewModel(); handleArguments(); + viewModel.loadInitialFormData(); binding.btnBack.setOnClickListener(v -> navigateBack()); binding.btnSavePet.setOnClickListener(v -> savePet()); @@ -72,23 +71,18 @@ public class PetDetailFragment extends Fragment { } private void observeViewModel() { - viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> updateCustomerSpinnerSelection()); - viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> updateStoreSpinnerSelection()); - - viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> { - if (resource == null) return; - setLoading(resource.status == Resource.Status.LOADING); - if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - viewModel.setCustomerList(resource.data); - } + viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); + + viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> { + PetDetailViewModel.ViewState state = viewModel.getViewState().getValue(); + Long selectedCustomerId = state != null ? state.selectedCustomerId : null; + updateCustomerSpinnerSelection(selectedCustomerId); }); - viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { - if (resource == null) return; - setLoading(resource.status == Resource.Status.LOADING); - if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - viewModel.setStoreList(resource.data); - } + viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> { + PetDetailViewModel.ViewState state = viewModel.getViewState().getValue(); + Long selectedStoreId = state != null ? state.selectedStoreId : null; + updateStoreSpinnerSelection(selectedStoreId); }); } @@ -119,12 +113,12 @@ public class PetDetailFragment extends Fragment { String status = binding.spinnerPetStatus.getSelectedItem().toString(); Long customerId = null; - if (binding.spinnerCustomer.getSelectedItemPosition() > 0) { + if (binding.spinnerCustomer.getSelectedItemPosition() > 0 && viewModel.getCustomerList().getValue() != null) { customerId = viewModel.getCustomerList().getValue().get(binding.spinnerCustomer.getSelectedItemPosition() - 1).getId(); } Long storeId = null; - if (binding.spinnerStore.getSelectedItemPosition() > 0) { + if (binding.spinnerStore.getSelectedItemPosition() > 0 && viewModel.getStoreList().getValue() != null) { storeId = viewModel.getStoreList().getValue().get(binding.spinnerStore.getSelectedItemPosition() - 1).getId(); } @@ -193,24 +187,12 @@ public class PetDetailFragment extends Fragment { private void handleArguments() { if (getArguments() != null && getArguments().containsKey("petId")) { - long petId = getArguments().getLong("petId"); - viewModel.setPetId(petId); - binding.tvMode.setText("Edit Pet"); - binding.tvPetId.setText(DateTimeUtils.formatId(petId)); - binding.tvPetId.setVisibility(View.VISIBLE); - binding.btnDeletePet.setVisibility(View.VISIBLE); - - UIUtils.setViewsEnabled(false, binding.etPetSpecies, binding.etPetBreed); + viewModel.setPetId(getArguments().getLong("petId")); loadPetData(); - } else { - viewModel.setPetId(-1); - binding.tvMode.setText("Add Pet"); - binding.tvPetId.setVisibility(View.GONE); - binding.btnDeletePet.setVisibility(View.GONE); - binding.btnSavePet.setText("Add"); - - UIUtils.setViewsEnabled(true, binding.etPetSpecies, binding.etPetBreed); + return; } + + viewModel.setPetId(-1); } private void loadPetData() { @@ -226,20 +208,13 @@ public class PetDetailFragment extends Fragment { if (p.getPetPrice() != null) { 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(); } }); } - private void updateCustomerSpinnerSelection() { + private void updateCustomerSpinnerSelection(Long selectedCustomerId) { SpinnerUtils.populateSpinner( requireContext(), binding.spinnerCustomer, @@ -251,7 +226,7 @@ public class PetDetailFragment extends Fragment { ); } - private void updateStoreSpinnerSelection() { + private void updateStoreSpinnerSelection(Long selectedStoreId) { SpinnerUtils.populateSpinner( requireContext(), binding.spinnerStore, @@ -264,36 +239,76 @@ public class PetDetailFragment extends Fragment { } private void setupSpinner() { - SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, - new String[]{"Available", "Adopted", "Owned"}); + SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, new String[]{}); - binding.spinnerPetStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - String status = parent.getItemAtPosition(position).toString(); - - clearSpinnerError(binding.spinnerCustomer); - clearSpinnerError(binding.spinnerStore); - - if ("Available".equalsIgnoreCase(status)) { - binding.spinnerCustomer.setSelection(0); - UIUtils.setViewsEnabled(false, binding.spinnerCustomer); - } else { - UIUtils.setViewsEnabled(true, binding.spinnerCustomer); - } - - if ("Owned".equalsIgnoreCase(status)) { - binding.spinnerStore.setSelection(0); - UIUtils.setViewsEnabled(false, binding.spinnerStore); - } else { - UIUtils.setViewsEnabled(true, binding.spinnerStore); - } + if (isUpdatingUI) return; + viewModel.onCustomerSelected(position); } @Override public void onNothingSelected(AdapterView parent) { } }); + + binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (isUpdatingUI) return; + viewModel.onStoreSelected(position); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + binding.spinnerPetStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (isUpdatingUI) return; + String status = parent.getItemAtPosition(position).toString(); + clearSpinnerError(binding.spinnerCustomer); + clearSpinnerError(binding.spinnerStore); + viewModel.onStatusSelected(status); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + } + + private void applyViewState(PetDetailViewModel.ViewState state) { + isUpdatingUI = true; + + binding.tvMode.setText(state.modeTitle); + binding.tvPetId.setText(DateTimeUtils.formatId(viewModel.getPetId())); + binding.tvPetId.setVisibility(state.isPetIdVisible ? View.VISIBLE : View.GONE); + binding.btnDeletePet.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE); + binding.btnSavePet.setText(state.saveButtonText); + + UIUtils.setViewsEnabled(state.isSpeciesEnabled, binding.etPetSpecies); + UIUtils.setViewsEnabled(state.isBreedEnabled, binding.etPetBreed); + UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerCustomer); + UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerStore); + + SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, state.availableStatuses); + SpinnerUtils.setSelectionByValue(binding.spinnerPetStatus, state.selectedStatus); + + updateCustomerSpinnerSelection(state.selectedCustomerId); + updateStoreSpinnerSelection(state.selectedStoreId); + + if (!state.isCustomerEnabled && binding.spinnerCustomer.getSelectedItemPosition() != 0) { + binding.spinnerCustomer.setSelection(0); + } + if (!state.isStoreEnabled && binding.spinnerStore.getSelectedItemPosition() != 0) { + binding.spinnerStore.setSelection(0); + } + + isUpdatingUI = false; } private void clearSpinnerError(Spinner spinner) { diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java index 2374fc4c..d7ee6e4c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java @@ -11,9 +11,9 @@ import androidx.navigation.fragment.NavHostFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import android.widget.Toast; -import com.example.petstoremobile.R; import com.example.petstoremobile.databinding.FragmentServiceDetailBinding; import com.example.petstoremobile.dtos.ServiceDTO; import com.example.petstoremobile.utils.ActivityLogger; @@ -21,6 +21,7 @@ import com.example.petstoremobile.utils.DateTimeUtils; import com.example.petstoremobile.utils.DialogUtils; import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.Resource; +import com.example.petstoremobile.utils.UIUtils; import com.example.petstoremobile.viewmodels.ServiceDetailViewModel; import dagger.hilt.android.AndroidEntryPoint; @@ -51,6 +52,7 @@ public class ServiceDetailFragment extends Fragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + observeViewModel(); handleArguments(); binding.btnBack.setOnClickListener(v -> navigateBack()); @@ -58,8 +60,12 @@ public class ServiceDetailFragment extends Fragment { binding.btnDeleteService.setOnClickListener(v -> deleteService()); } + private void observeViewModel() { + viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); + } + private void setLoading(boolean loading) { - if (binding != null && binding.progressBar != null) { + if (binding != null) { binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); } } @@ -126,34 +132,48 @@ public class ServiceDetailFragment extends Fragment { private void handleArguments() { if (getArguments() != null && getArguments().containsKey("serviceId")) { - long serviceId = getArguments().getLong("serviceId"); - viewModel.setServiceId(serviceId); - binding.tvMode.setText("Edit Service"); - binding.tvServiceId.setText(DateTimeUtils.formatId(serviceId)); - binding.btnDeleteService.setVisibility(View.VISIBLE); + viewModel.setServiceId(getArguments().getLong("serviceId")); loadServiceData(); - } else { - viewModel.setServiceId(-1); - binding.tvMode.setText("Add Service"); - binding.tvServiceId.setVisibility(View.GONE); - binding.btnDeleteService.setVisibility(View.GONE); - binding.btnSaveService.setText("Add"); + return; } + + viewModel.setServiceId(-1); } private void loadServiceData() { viewModel.loadService().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; setLoading(resource.status == Resource.Status.LOADING); - if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - ServiceDTO s = resource.data; - binding.etServiceName.setText(s.getServiceName()); - binding.etServiceDesc.setText(s.getServiceDesc()); - binding.etServiceDuration.setText(String.valueOf(s.getServiceDuration())); - binding.etServicePrice.setText(String.valueOf(s.getServicePrice())); - } else if (resource.status == Resource.Status.ERROR) { + if (resource.status == Resource.Status.ERROR) { Toast.makeText(getContext(), "Failed to load service: " + resource.message, Toast.LENGTH_SHORT).show(); } }); } + + private void applyViewState(ServiceDetailViewModel.ViewState state) { + binding.tvMode.setText(state.modeTitle); + binding.tvServiceId.setText(DateTimeUtils.formatId(viewModel.getServiceId())); + binding.tvServiceId.setVisibility(state.isServiceIdVisible ? View.VISIBLE : View.GONE); + binding.btnDeleteService.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE); + binding.btnSaveService.setText(state.saveButtonText); + + UIUtils.setViewsEnabled(state.isFieldsEnabled, + binding.etServiceName, + binding.etServiceDesc, + binding.etServiceDuration, + binding.etServicePrice); + + updateIfDifferent(binding.etServiceName, state.serviceName); + updateIfDifferent(binding.etServiceDesc, state.serviceDesc); + updateIfDifferent(binding.etServiceDuration, state.serviceDuration); + updateIfDifferent(binding.etServicePrice, state.servicePrice); + } + + private void updateIfDifferent(EditText field, String value) { + String current = field.getText() != null ? field.getText().toString() : ""; + String next = value != null ? value : ""; + if (!current.equals(next)) { + field.setText(next); + } + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java b/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java index bf8c8770..bf45f4f8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/FileUtils.java @@ -11,13 +11,31 @@ import java.io.InputStream; public class FileUtils { public static File getFileFromUri(Context context, Uri uri) { try { + if ("content".equals(uri.getScheme())) { + String authority = uri.getAuthority(); + if (authority != null && authority.equals(context.getPackageName() + ".fileprovider")) { + String lastSegment = uri.getLastPathSegment(); + if (lastSegment != null) { + String fileName = lastSegment.contains("/") + ? lastSegment.substring(lastSegment.lastIndexOf('/') + 1) + : lastSegment; + File cachedFile = new File(context.getCacheDir(), fileName); + if (cachedFile.exists() && cachedFile.length() > 0) { + return cachedFile; + } + } + } + } + String fileName = getFileName(context, uri); if (fileName == null) fileName = "upload_" + System.currentTimeMillis(); - + InputStream inputStream = context.getContentResolver().openInputStream(uri); + if (inputStream == null) return null; + File tempFile = new File(context.getCacheDir(), fileName); FileOutputStream outputStream = new FileOutputStream(tempFile); - byte[] buffer = new byte[1024]; + byte[] buffer = new byte[4096]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); @@ -47,4 +65,4 @@ public class FileUtils { } return result; } -} +} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/ImagePickerHelper.java b/android/app/src/main/java/com/example/petstoremobile/utils/ImagePickerHelper.java index 4d1e9bf9..2bcbb52a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/ImagePickerHelper.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/ImagePickerHelper.java @@ -129,9 +129,16 @@ public class ImagePickerHelper { * Prepares a temporary file and launches the camera app. */ private void launchCamera() { - File photoFile = new File(fragment.requireContext().getCacheDir(), tempFileName); - photoUri = FileProvider.getUriForFile(fragment.requireContext(), fragment.requireContext().getPackageName() + ".fileprovider", photoFile); - cameraLauncher.launch(photoUri); + try { + File photoFile = new File(fragment.requireContext().getCacheDir(), tempFileName); + if (!photoFile.exists()) photoFile.createNewFile(); + photoUri = FileProvider.getUriForFile(fragment.requireContext(), + fragment.requireContext().getPackageName() + ".fileprovider", photoFile); + cameraLauncher.launch(photoUri); + } catch (Exception e) { + android.widget.Toast.makeText(fragment.requireContext(), + "Could not prepare camera", android.widget.Toast.LENGTH_SHORT).show(); + } } /** @@ -157,4 +164,4 @@ public class ImagePickerHelper { .setNegativeButton("Cancel", null) .show(); } -} +} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java index 68506f44..44f2680a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java @@ -20,17 +20,22 @@ import dagger.hilt.android.lifecycle.HiltViewModel; @HiltViewModel public class PetDetailViewModel extends ViewModel { + private static final String STATUS_AVAILABLE = "Available"; + private static final String STATUS_ADOPTED = "Adopted"; + private static final String STATUS_OWNED = "Owned"; + private final PetRepository petRepository; private final CustomerRepository customerRepository; private final StoreRepository storeRepository; - private final MutableLiveData petState = new MutableLiveData<>(); private final MutableLiveData> customerList = new MutableLiveData<>(new ArrayList<>()); private final MutableLiveData> storeList = new MutableLiveData<>(new ArrayList<>()); private final MutableLiveData isLoading = new MutableLiveData<>(false); - + private final MutableLiveData viewState = new MutableLiveData<>(new ViewState()); + private long petId = -1; - private boolean isEditing = false; + private Long selectedCustomerId = null; + private Long selectedStoreId = null; @Inject public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) { @@ -39,9 +44,23 @@ public class PetDetailViewModel extends ViewModel { this.storeRepository = storeRepository; } + public void loadInitialFormData() { + customerRepository.getCustomerDropdowns().observeForever(resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + customerList.setValue(resource.data); + } + }); + + storeRepository.getStoreDropdowns().observeForever(resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + storeList.setValue(resource.data); + } + }); + } + public void setPetId(long id) { this.petId = id; - this.isEditing = id != -1; + initMode(id != -1); } public long getPetId() { @@ -49,46 +68,108 @@ public class PetDetailViewModel extends ViewModel { } public boolean isEditing() { - return isEditing; + ViewState current = viewState.getValue(); + return current != null && current.isEditing; + } + + public LiveData getViewState() { + return viewState; + } + + public void onCustomerSelected(int position) { + List list = customerList.getValue(); + if (position > 0 && list != null && position <= list.size()) { + selectedCustomerId = list.get(position - 1).getId(); + } else { + selectedCustomerId = null; + } + + updateViewState(state -> state.selectedCustomerId = selectedCustomerId); + } + + public void onStoreSelected(int position) { + List list = storeList.getValue(); + if (position > 0 && list != null && position <= list.size()) { + selectedStoreId = list.get(position - 1).getId(); + } else { + selectedStoreId = null; + } + + updateViewState(state -> state.selectedStoreId = selectedStoreId); + } + + public void onStatusSelected(String status) { + updateViewState(state -> { + state.selectedStatus = normalizeStatus(status); + applyStatusRules(state, true); + }); + } + + public void initMode(boolean isEditing) { + updateViewState(state -> { + state.isEditing = isEditing; + state.modeTitle = isEditing ? "Edit Pet" : "Add Pet"; + state.saveButtonText = isEditing ? "Save" : "Add"; + state.isPetIdVisible = isEditing; + state.isDeleteVisible = isEditing; + state.isSpeciesEnabled = !isEditing; + state.isBreedEnabled = !isEditing; + + if (isEditing) { + state.isCustomerEnabled = true; + state.isStoreEnabled = true; + } + + if (!isEditing) { + selectedCustomerId = null; + selectedStoreId = null; + state.selectedCustomerId = null; + state.selectedStoreId = null; + state.selectedStatus = STATUS_AVAILABLE; + state.isCustomerEnabled = false; + state.isStoreEnabled = true; + } + }); } public LiveData> loadPet() { - return petRepository.getPetById(petId); - } + MutableLiveData> result = new MutableLiveData<>(); + petRepository.getPetById(petId).observeForever(resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + PetDTO pet = resource.data; + selectedCustomerId = pet.getCustomerId(); + selectedStoreId = pet.getStoreId(); - public LiveData>> loadCustomers() { - return customerRepository.getCustomerDropdowns(); - } + updateViewState(state -> { + state.selectedCustomerId = selectedCustomerId; + state.selectedStoreId = selectedStoreId; + state.selectedStatus = normalizeStatus(pet.getPetStatus()); + applyStatusRules(state, false); + }); + } - public LiveData>> loadStores() { - return storeRepository.getStoreDropdowns(); + result.setValue(resource); + }); + return result; } public LiveData> savePet(PetDTO petDTO) { - if (isEditing) { + if (isEditing()) { petDTO.setPetId(petId); return petRepository.updatePet(petId, petDTO); - } else { - return petRepository.createPet(petDTO); } + + return petRepository.createPet(petDTO); } public LiveData> deletePet() { return petRepository.deletePet(petId); } - public void setCustomerList(List list) { - customerList.setValue(list); - } - public LiveData> getCustomerList() { return customerList; } - public void setStoreList(List list) { - storeList.setValue(list); - } - public LiveData> getStoreList() { return storeList; } @@ -100,4 +181,66 @@ public class PetDetailViewModel extends ViewModel { public void setLoading(boolean loading) { isLoading.setValue(loading); } + + private void applyStatusRules(ViewState state, boolean clearInvalidSelections) { + if (STATUS_AVAILABLE.equalsIgnoreCase(state.selectedStatus)) { + state.isCustomerEnabled = false; + state.isStoreEnabled = true; + if (clearInvalidSelections) { + selectedCustomerId = null; + state.selectedCustomerId = null; + } + return; + } + + if (STATUS_OWNED.equalsIgnoreCase(state.selectedStatus)) { + state.isCustomerEnabled = true; + state.isStoreEnabled = false; + if (clearInvalidSelections) { + selectedStoreId = null; + state.selectedStoreId = null; + } + return; + } + + state.isCustomerEnabled = true; + state.isStoreEnabled = true; + } + + private String normalizeStatus(String status) { + if (status == null) return STATUS_AVAILABLE; + String normalized = status.trim(); + if (STATUS_ADOPTED.equalsIgnoreCase(normalized)) return STATUS_ADOPTED; + if (STATUS_OWNED.equalsIgnoreCase(normalized)) return STATUS_OWNED; + return STATUS_AVAILABLE; + } + + private void updateViewState(Action action) { + ViewState current = viewState.getValue(); + if (current != null) { + action.run(current); + viewState.setValue(current); + } + } + + private interface Action { + void run(T target); + } + + + public static class ViewState { + public boolean isEditing = false; + public boolean isDeleteVisible = false; + public boolean isPetIdVisible = false; + public boolean isSpeciesEnabled = true; + public boolean isBreedEnabled = true; + public boolean isCustomerEnabled = false; + public boolean isStoreEnabled = true; + public String modeTitle = "Add Pet"; + public String saveButtonText = "Add"; + public String[] availableStatuses = new String[]{STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED}; + public String selectedStatus = STATUS_AVAILABLE; + public Long selectedCustomerId = null; + public Long selectedStoreId = null; + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java index fca74229..aa465a08 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java @@ -1,6 +1,7 @@ package com.example.petstoremobile.viewmodels; import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.example.petstoremobile.dtos.ServiceDTO; @@ -14,8 +15,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel; @HiltViewModel public class ServiceDetailViewModel extends ViewModel { private final ServiceRepository repository; + private final MutableLiveData viewState = new MutableLiveData<>(new ViewState()); + private long serviceId = -1; - private boolean isEditing = false; @Inject public ServiceDetailViewModel(ServiceRepository repository) { @@ -24,7 +26,7 @@ public class ServiceDetailViewModel extends ViewModel { public void setServiceId(long id) { this.serviceId = id; - this.isEditing = id != -1; + initMode(id != -1); } public long getServiceId() { @@ -32,23 +34,88 @@ public class ServiceDetailViewModel extends ViewModel { } public boolean isEditing() { - return isEditing; + ViewState current = viewState.getValue(); + return current != null && current.isEditing; + } + + public LiveData getViewState() { + return viewState; + } + + public void initMode(boolean isEditing) { + updateViewState(state -> { + state.isEditing = isEditing; + state.modeTitle = isEditing ? "Edit Service" : "Add Service"; + state.saveButtonText = isEditing ? "Save" : "Add"; + state.isServiceIdVisible = isEditing; + state.isDeleteVisible = isEditing; + state.isFieldsEnabled = true; + }); } public LiveData> loadService() { - return repository.getServiceById(serviceId); + MutableLiveData> result = new MutableLiveData<>(); + repository.getServiceById(serviceId).observeForever(resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + ServiceDTO service = resource.data; + updateViewState(state -> { + state.serviceName = safeText(service.getServiceName()); + state.serviceDesc = safeText(service.getServiceDesc()); + state.serviceDuration = service.getServiceDuration() != null ? String.valueOf(service.getServiceDuration()) : ""; + state.servicePrice = service.getServicePrice() != null ? String.valueOf(service.getServicePrice()) : ""; + }); + } + result.setValue(resource); + }); + return result; } public LiveData> saveService(ServiceDTO dto) { - if (isEditing) { + updateViewState(state -> { + state.serviceName = safeText(dto.getServiceName()); + state.serviceDesc = safeText(dto.getServiceDesc()); + state.serviceDuration = dto.getServiceDuration() != null ? String.valueOf(dto.getServiceDuration()) : ""; + state.servicePrice = dto.getServicePrice() != null ? String.valueOf(dto.getServicePrice()) : ""; + }); + + if (isEditing()) { dto.setServiceId(serviceId); return repository.updateService(serviceId, dto); - } else { - return repository.createService(dto); } + + return repository.createService(dto); } public LiveData> deleteService() { return repository.deleteService(serviceId); } + + private String safeText(String value) { + return value == null ? "" : value.trim(); + } + + private void updateViewState(Action action) { + ViewState current = viewState.getValue(); + if (current != null) { + action.run(current); + viewState.setValue(current); + } + } + + private interface Action { + void run(T target); + } + + public static class ViewState { + public boolean isEditing = false; + public boolean isDeleteVisible = false; + public boolean isServiceIdVisible = false; + public boolean isFieldsEnabled = true; + public String modeTitle = "Add Service"; + public String saveButtonText = "Add"; + public String serviceName = ""; + public String serviceDesc = ""; + public String serviceDuration = ""; + public String servicePrice = ""; + } }