Fixed profile issue with camera and added viewstate to pet and service

This commit is contained in:
Alex
2026-04-09 23:39:34 -06:00
parent 1dd350fc8a
commit 0ee097e82d
7 changed files with 398 additions and 125 deletions

View File

@@ -56,11 +56,14 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
setupFilterToggle(); setupFilterToggle();
setupBulkDelete(); setupBulkDelete();
observeViewModel(); observeViewModel();
loadServices(true); loadServices(true);
UIUtils.setupHamburgerMenu(binding.btnHamburger, this); UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
binding.fabAddService.setOnClickListener(v ->
NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail));
return binding.getRoot(); return binding.getRoot();
} }
@@ -156,4 +159,4 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
bulkDeleteHandler.onSelectionChanged(count); bulkDeleteHandler.onSelectionChanged(count);
} }
} }
} }

View File

@@ -41,9 +41,7 @@ public class PetDetailFragment extends Fragment {
private FragmentPetDetailBinding binding; private FragmentPetDetailBinding binding;
private PetDetailViewModel viewModel; private PetDetailViewModel viewModel;
private boolean isUpdatingUI = false;
private Long selectedCustomerId = null;
private Long selectedStoreId = null;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@@ -65,6 +63,7 @@ public class PetDetailFragment extends Fragment {
setupSpinner(); setupSpinner();
observeViewModel(); observeViewModel();
handleArguments(); handleArguments();
viewModel.loadInitialFormData();
binding.btnBack.setOnClickListener(v -> navigateBack()); binding.btnBack.setOnClickListener(v -> navigateBack());
binding.btnSavePet.setOnClickListener(v -> savePet()); binding.btnSavePet.setOnClickListener(v -> savePet());
@@ -72,23 +71,18 @@ public class PetDetailFragment extends Fragment {
} }
private void observeViewModel() { private void observeViewModel() {
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> updateCustomerSpinnerSelection()); viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> updateStoreSpinnerSelection());
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> {
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> { PetDetailViewModel.ViewState state = viewModel.getViewState().getValue();
if (resource == null) return; Long selectedCustomerId = state != null ? state.selectedCustomerId : null;
setLoading(resource.status == Resource.Status.LOADING); updateCustomerSpinnerSelection(selectedCustomerId);
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
viewModel.setCustomerList(resource.data);
}
}); });
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
if (resource == null) return; PetDetailViewModel.ViewState state = viewModel.getViewState().getValue();
setLoading(resource.status == Resource.Status.LOADING); Long selectedStoreId = state != null ? state.selectedStoreId : null;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) { updateStoreSpinnerSelection(selectedStoreId);
viewModel.setStoreList(resource.data);
}
}); });
} }
@@ -119,12 +113,12 @@ public class PetDetailFragment extends Fragment {
String status = binding.spinnerPetStatus.getSelectedItem().toString(); String status = binding.spinnerPetStatus.getSelectedItem().toString();
Long customerId = null; 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(); customerId = viewModel.getCustomerList().getValue().get(binding.spinnerCustomer.getSelectedItemPosition() - 1).getId();
} }
Long storeId = null; 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(); storeId = viewModel.getStoreList().getValue().get(binding.spinnerStore.getSelectedItemPosition() - 1).getId();
} }
@@ -193,24 +187,12 @@ public class PetDetailFragment extends Fragment {
private void handleArguments() { private void handleArguments() {
if (getArguments() != null && getArguments().containsKey("petId")) { if (getArguments() != null && getArguments().containsKey("petId")) {
long petId = getArguments().getLong("petId"); viewModel.setPetId(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);
loadPetData(); loadPetData();
} else { return;
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);
} }
viewModel.setPetId(-1);
} }
private void loadPetData() { private void loadPetData() {
@@ -226,20 +208,13 @@ public class PetDetailFragment extends Fragment {
if (p.getPetPrice() != null) { if (p.getPetPrice() != null) {
binding.etPetPrice.setText(String.format(Locale.getDefault(), "%.2f", p.getPetPrice())); 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) { } else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Failed to load pet: " + resource.message, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Failed to load pet: " + resource.message, Toast.LENGTH_SHORT).show();
} }
}); });
} }
private void updateCustomerSpinnerSelection() { private void updateCustomerSpinnerSelection(Long selectedCustomerId) {
SpinnerUtils.populateSpinner( SpinnerUtils.populateSpinner(
requireContext(), requireContext(),
binding.spinnerCustomer, binding.spinnerCustomer,
@@ -251,7 +226,7 @@ public class PetDetailFragment extends Fragment {
); );
} }
private void updateStoreSpinnerSelection() { private void updateStoreSpinnerSelection(Long selectedStoreId) {
SpinnerUtils.populateSpinner( SpinnerUtils.populateSpinner(
requireContext(), requireContext(),
binding.spinnerStore, binding.spinnerStore,
@@ -264,36 +239,76 @@ public class PetDetailFragment extends Fragment {
} }
private void setupSpinner() { private void setupSpinner() {
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, new String[]{});
new String[]{"Available", "Adopted", "Owned"});
binding.spinnerPetStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String status = parent.getItemAtPosition(position).toString(); if (isUpdatingUI) return;
viewModel.onCustomerSelected(position);
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);
}
} }
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { 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) { private void clearSpinnerError(Spinner spinner) {

View File

@@ -11,9 +11,9 @@ import androidx.navigation.fragment.NavHostFragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.FragmentServiceDetailBinding; import com.example.petstoremobile.databinding.FragmentServiceDetailBinding;
import com.example.petstoremobile.dtos.ServiceDTO; import com.example.petstoremobile.dtos.ServiceDTO;
import com.example.petstoremobile.utils.ActivityLogger; 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.DialogUtils;
import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.InputValidator;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.ServiceDetailViewModel; import com.example.petstoremobile.viewmodels.ServiceDetailViewModel;
import dagger.hilt.android.AndroidEntryPoint; import dagger.hilt.android.AndroidEntryPoint;
@@ -51,6 +52,7 @@ public class ServiceDetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
observeViewModel();
handleArguments(); handleArguments();
binding.btnBack.setOnClickListener(v -> navigateBack()); binding.btnBack.setOnClickListener(v -> navigateBack());
@@ -58,8 +60,12 @@ public class ServiceDetailFragment extends Fragment {
binding.btnDeleteService.setOnClickListener(v -> deleteService()); binding.btnDeleteService.setOnClickListener(v -> deleteService());
} }
private void observeViewModel() {
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
}
private void setLoading(boolean loading) { private void setLoading(boolean loading) {
if (binding != null && binding.progressBar != null) { if (binding != null) {
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE); binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
} }
} }
@@ -126,34 +132,48 @@ public class ServiceDetailFragment extends Fragment {
private void handleArguments() { private void handleArguments() {
if (getArguments() != null && getArguments().containsKey("serviceId")) { if (getArguments() != null && getArguments().containsKey("serviceId")) {
long serviceId = getArguments().getLong("serviceId"); viewModel.setServiceId(getArguments().getLong("serviceId"));
viewModel.setServiceId(serviceId);
binding.tvMode.setText("Edit Service");
binding.tvServiceId.setText(DateTimeUtils.formatId(serviceId));
binding.btnDeleteService.setVisibility(View.VISIBLE);
loadServiceData(); loadServiceData();
} else { return;
viewModel.setServiceId(-1);
binding.tvMode.setText("Add Service");
binding.tvServiceId.setVisibility(View.GONE);
binding.btnDeleteService.setVisibility(View.GONE);
binding.btnSaveService.setText("Add");
} }
viewModel.setServiceId(-1);
} }
private void loadServiceData() { private void loadServiceData() {
viewModel.loadService().observe(getViewLifecycleOwner(), resource -> { viewModel.loadService().observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return; if (resource == null) return;
setLoading(resource.status == Resource.Status.LOADING); setLoading(resource.status == Resource.Status.LOADING);
if (resource.status == Resource.Status.SUCCESS && resource.data != null) { if (resource.status == Resource.Status.ERROR) {
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) {
Toast.makeText(getContext(), "Failed to load service: " + resource.message, Toast.LENGTH_SHORT).show(); 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);
}
}
} }

View File

@@ -11,13 +11,31 @@ import java.io.InputStream;
public class FileUtils { public class FileUtils {
public static File getFileFromUri(Context context, Uri uri) { public static File getFileFromUri(Context context, Uri uri) {
try { 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); String fileName = getFileName(context, uri);
if (fileName == null) fileName = "upload_" + System.currentTimeMillis(); if (fileName == null) fileName = "upload_" + System.currentTimeMillis();
InputStream inputStream = context.getContentResolver().openInputStream(uri); InputStream inputStream = context.getContentResolver().openInputStream(uri);
if (inputStream == null) return null;
File tempFile = new File(context.getCacheDir(), fileName); File tempFile = new File(context.getCacheDir(), fileName);
FileOutputStream outputStream = new FileOutputStream(tempFile); FileOutputStream outputStream = new FileOutputStream(tempFile);
byte[] buffer = new byte[1024]; byte[] buffer = new byte[4096];
int length; int length;
while ((length = inputStream.read(buffer)) > 0) { while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length); outputStream.write(buffer, 0, length);
@@ -47,4 +65,4 @@ public class FileUtils {
} }
return result; return result;
} }
} }

View File

@@ -129,9 +129,16 @@ public class ImagePickerHelper {
* Prepares a temporary file and launches the camera app. * Prepares a temporary file and launches the camera app.
*/ */
private void launchCamera() { private void launchCamera() {
File photoFile = new File(fragment.requireContext().getCacheDir(), tempFileName); try {
photoUri = FileProvider.getUriForFile(fragment.requireContext(), fragment.requireContext().getPackageName() + ".fileprovider", photoFile); File photoFile = new File(fragment.requireContext().getCacheDir(), tempFileName);
cameraLauncher.launch(photoUri); 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) .setNegativeButton("Cancel", null)
.show(); .show();
} }
} }

View File

@@ -20,17 +20,22 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel @HiltViewModel
public class PetDetailViewModel extends ViewModel { 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 PetRepository petRepository;
private final CustomerRepository customerRepository; private final CustomerRepository customerRepository;
private final StoreRepository storeRepository; private final StoreRepository storeRepository;
private final MutableLiveData<PetDTO> petState = new MutableLiveData<>();
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>()); private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>()); private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false); private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
private long petId = -1; private long petId = -1;
private boolean isEditing = false; private Long selectedCustomerId = null;
private Long selectedStoreId = null;
@Inject @Inject
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) { public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
@@ -39,9 +44,23 @@ public class PetDetailViewModel extends ViewModel {
this.storeRepository = storeRepository; 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) { public void setPetId(long id) {
this.petId = id; this.petId = id;
this.isEditing = id != -1; initMode(id != -1);
} }
public long getPetId() { public long getPetId() {
@@ -49,46 +68,108 @@ public class PetDetailViewModel extends ViewModel {
} }
public boolean isEditing() { public boolean isEditing() {
return isEditing; ViewState current = viewState.getValue();
return current != null && current.isEditing;
}
public LiveData<ViewState> getViewState() {
return viewState;
}
public void onCustomerSelected(int position) {
List<DropdownDTO> 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<DropdownDTO> 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<Resource<PetDTO>> loadPet() { public LiveData<Resource<PetDTO>> loadPet() {
return petRepository.getPetById(petId); MutableLiveData<Resource<PetDTO>> 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<Resource<List<DropdownDTO>>> loadCustomers() { updateViewState(state -> {
return customerRepository.getCustomerDropdowns(); state.selectedCustomerId = selectedCustomerId;
} state.selectedStoreId = selectedStoreId;
state.selectedStatus = normalizeStatus(pet.getPetStatus());
applyStatusRules(state, false);
});
}
public LiveData<Resource<List<DropdownDTO>>> loadStores() { result.setValue(resource);
return storeRepository.getStoreDropdowns(); });
return result;
} }
public LiveData<Resource<PetDTO>> savePet(PetDTO petDTO) { public LiveData<Resource<PetDTO>> savePet(PetDTO petDTO) {
if (isEditing) { if (isEditing()) {
petDTO.setPetId(petId); petDTO.setPetId(petId);
return petRepository.updatePet(petId, petDTO); return petRepository.updatePet(petId, petDTO);
} else {
return petRepository.createPet(petDTO);
} }
return petRepository.createPet(petDTO);
} }
public LiveData<Resource<Void>> deletePet() { public LiveData<Resource<Void>> deletePet() {
return petRepository.deletePet(petId); return petRepository.deletePet(petId);
} }
public void setCustomerList(List<DropdownDTO> list) {
customerList.setValue(list);
}
public LiveData<List<DropdownDTO>> getCustomerList() { public LiveData<List<DropdownDTO>> getCustomerList() {
return customerList; return customerList;
} }
public void setStoreList(List<DropdownDTO> list) {
storeList.setValue(list);
}
public LiveData<List<DropdownDTO>> getStoreList() { public LiveData<List<DropdownDTO>> getStoreList() {
return storeList; return storeList;
} }
@@ -100,4 +181,66 @@ public class PetDetailViewModel extends ViewModel {
public void setLoading(boolean loading) { public void setLoading(boolean loading) {
isLoading.setValue(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<ViewState> action) {
ViewState current = viewState.getValue();
if (current != null) {
action.run(current);
viewState.setValue(current);
}
}
private interface Action<T> {
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;
}
} }

View File

@@ -1,6 +1,7 @@
package com.example.petstoremobile.viewmodels; package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.ServiceDTO; import com.example.petstoremobile.dtos.ServiceDTO;
@@ -14,8 +15,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel @HiltViewModel
public class ServiceDetailViewModel extends ViewModel { public class ServiceDetailViewModel extends ViewModel {
private final ServiceRepository repository; private final ServiceRepository repository;
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
private long serviceId = -1; private long serviceId = -1;
private boolean isEditing = false;
@Inject @Inject
public ServiceDetailViewModel(ServiceRepository repository) { public ServiceDetailViewModel(ServiceRepository repository) {
@@ -24,7 +26,7 @@ public class ServiceDetailViewModel extends ViewModel {
public void setServiceId(long id) { public void setServiceId(long id) {
this.serviceId = id; this.serviceId = id;
this.isEditing = id != -1; initMode(id != -1);
} }
public long getServiceId() { public long getServiceId() {
@@ -32,23 +34,88 @@ public class ServiceDetailViewModel extends ViewModel {
} }
public boolean isEditing() { public boolean isEditing() {
return isEditing; ViewState current = viewState.getValue();
return current != null && current.isEditing;
}
public LiveData<ViewState> 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<Resource<ServiceDTO>> loadService() { public LiveData<Resource<ServiceDTO>> loadService() {
return repository.getServiceById(serviceId); MutableLiveData<Resource<ServiceDTO>> 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<Resource<ServiceDTO>> saveService(ServiceDTO dto) { public LiveData<Resource<ServiceDTO>> 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); dto.setServiceId(serviceId);
return repository.updateService(serviceId, dto); return repository.updateService(serviceId, dto);
} else {
return repository.createService(dto);
} }
return repository.createService(dto);
} }
public LiveData<Resource<Void>> deleteService() { public LiveData<Resource<Void>> deleteService() {
return repository.deleteService(serviceId); return repository.deleteService(serviceId);
} }
private String safeText(String value) {
return value == null ? "" : value.trim();
}
private void updateViewState(Action<ViewState> action) {
ViewState current = viewState.getValue();
if (current != null) {
action.run(current);
viewState.setValue(current);
}
}
private interface Action<T> {
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 = "";
}
} }