merge final branch #338

Merged
RecentRunner merged 1 commits from final into main 2026-04-20 21:33:19 -06:00

View File

@@ -20,6 +20,7 @@ import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel; import dagger.hilt.android.lifecycle.HiltViewModel;
// ViewModel for the pet detail/create screen
@HiltViewModel @HiltViewModel
public class PetDetailViewModel extends ViewModel { public class PetDetailViewModel extends ViewModel {
private static final String STATUS_AVAILABLE = "Available"; private static final String STATUS_AVAILABLE = "Available";
@@ -38,13 +39,13 @@ public class PetDetailViewModel extends ViewModel {
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 final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
private long petId = -1; private long petId = -1; // -1 means creating a new pet
private Long selectedCustomerId = null; private Long selectedCustomerId = null;
private Long selectedStoreId = null; private Long selectedStoreId = null;
private String selectedSpecies = null; private String selectedSpecies = null;
private String selectedBreed = null; private String selectedBreed = null;
private boolean isOriginallyOwnedOrAdopted = false; private boolean isOriginallyOwnedOrAdopted = false; // used to restrict staff edits
private Long originalCustomerId = null; private Long originalCustomerId = null; // used to detect owner changes
@Inject @Inject
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) { public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
@@ -53,6 +54,7 @@ public class PetDetailViewModel extends ViewModel {
this.storeRepository = storeRepository; this.storeRepository = storeRepository;
} }
// Loads customers, stores, and species dropdowns needed for the form
public void loadInitialFormData() { public void loadInitialFormData() {
observeOnce(customerRepository.getCustomerDropdowns(), resource -> { observeOnce(customerRepository.getCustomerDropdowns(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
@@ -78,26 +80,18 @@ public class PetDetailViewModel extends ViewModel {
initMode(id != -1); initMode(id != -1);
} }
public long getPetId() { public long getPetId() { return petId; }
return petId;
}
public boolean isEditing() { public boolean isEditing() {
ViewState current = viewState.getValue(); ViewState current = viewState.getValue();
return current != null && current.isEditing; return current != null && current.isEditing;
} }
public boolean isOriginallyOwnedOrAdopted() { public boolean isOriginallyOwnedOrAdopted() { return isOriginallyOwnedOrAdopted; }
return isOriginallyOwnedOrAdopted;
}
public Long getOriginalCustomerId() { public Long getOriginalCustomerId() { return originalCustomerId; }
return originalCustomerId;
}
public LiveData<ViewState> getViewState() { public LiveData<ViewState> getViewState() { return viewState; }
return viewState;
}
public void onCustomerSelected(int position) { public void onCustomerSelected(int position) {
List<DropdownDTO> list = customerList.getValue(); List<DropdownDTO> list = customerList.getValue();
@@ -106,7 +100,6 @@ public class PetDetailViewModel extends ViewModel {
} else { } else {
selectedCustomerId = null; selectedCustomerId = null;
} }
updateViewState(state -> state.selectedCustomerId = selectedCustomerId); updateViewState(state -> state.selectedCustomerId = selectedCustomerId);
} }
@@ -114,7 +107,7 @@ public class PetDetailViewModel extends ViewModel {
List<DropdownDTO> list = speciesList.getValue(); List<DropdownDTO> list = speciesList.getValue();
if (position > 0 && list != null && position <= list.size()) { if (position > 0 && list != null && position <= list.size()) {
selectedSpecies = list.get(position - 1).getLabel(); selectedSpecies = list.get(position - 1).getLabel();
loadBreeds(selectedSpecies); loadBreeds(selectedSpecies); // reload breeds when species changes
} else { } else {
selectedSpecies = null; selectedSpecies = null;
breedList.setValue(new ArrayList<>()); breedList.setValue(new ArrayList<>());
@@ -150,17 +143,17 @@ public class PetDetailViewModel extends ViewModel {
} else { } else {
selectedStoreId = null; selectedStoreId = null;
} }
updateViewState(state -> state.selectedStoreId = selectedStoreId); updateViewState(state -> state.selectedStoreId = selectedStoreId);
} }
public void onStatusSelected(String status) { public void onStatusSelected(String status) {
updateViewState(state -> { updateViewState(state -> {
state.selectedStatus = normalizeStatus(status); state.selectedStatus = normalizeStatus(status);
applyStatusRules(state, true); applyStatusRules(state, true); // clear invalid selections when status changes
}); });
} }
// Sets up initial ViewState based on whether we're editing or creating
public void initMode(boolean isEditing) { public void initMode(boolean isEditing) {
updateViewState(state -> { updateViewState(state -> {
state.isEditing = isEditing; state.isEditing = isEditing;
@@ -177,6 +170,7 @@ public class PetDetailViewModel extends ViewModel {
} }
if (!isEditing) { if (!isEditing) {
// Reset all selections for a blank create form
selectedCustomerId = null; selectedCustomerId = null;
selectedStoreId = null; selectedStoreId = null;
selectedSpecies = null; selectedSpecies = null;
@@ -202,6 +196,7 @@ public class PetDetailViewModel extends ViewModel {
selectedStoreId = pet.getStoreId(); selectedStoreId = pet.getStoreId();
selectedSpecies = pet.getPetSpecies(); selectedSpecies = pet.getPetSpecies();
selectedBreed = pet.getPetBreed(); selectedBreed = pet.getPetBreed();
// Track original status to restrict staff from editing owned/adopted pets
isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus()) isOriginallyOwnedOrAdopted = STATUS_OWNED.equalsIgnoreCase(pet.getPetStatus())
|| STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus()); || STATUS_ADOPTED.equalsIgnoreCase(pet.getPetStatus());
originalCustomerId = pet.getCustomerId(); originalCustomerId = pet.getCustomerId();
@@ -217,7 +212,7 @@ public class PetDetailViewModel extends ViewModel {
state.selectedBreed = selectedBreed; state.selectedBreed = selectedBreed;
state.selectedStatus = normalizeStatus(pet.getPetStatus()); state.selectedStatus = normalizeStatus(pet.getPetStatus());
state.isBreedEnabled = !state.isEditing && (selectedSpecies != null); state.isBreedEnabled = !state.isEditing && (selectedSpecies != null);
applyStatusRules(state, false); applyStatusRules(state, false); // don't clear selections when loading existing data
}); });
} }
@@ -231,46 +226,26 @@ public class PetDetailViewModel extends ViewModel {
petDTO.setPetId(petId); petDTO.setPetId(petId);
return petRepository.updatePet(petId, petDTO); return petRepository.updatePet(petId, petDTO);
} }
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 LiveData<Resource<Void>> uploadPetImage(okhttp3.MultipartBody.Part image) { public LiveData<Resource<Void>> uploadPetImage(okhttp3.MultipartBody.Part image) {
return petRepository.uploadPetImage(petId, image); return petRepository.uploadPetImage(petId, image);
} }
public LiveData<Resource<Void>> deletePetImage() { public LiveData<Resource<Void>> deletePetImage() { return petRepository.deletePetImage(petId); }
return petRepository.deletePetImage(petId);
}
public LiveData<List<DropdownDTO>> getCustomerList() { public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
return customerList; public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
} public LiveData<List<DropdownDTO>> getSpeciesList() { return speciesList; }
public LiveData<List<DropdownDTO>> getBreedList() { return breedList; }
public LiveData<Boolean> getIsLoading() { return isLoading; }
public LiveData<List<DropdownDTO>> getStoreList() { public void setLoading(boolean loading) { isLoading.setValue(loading); }
return storeList;
}
public LiveData<List<DropdownDTO>> getSpeciesList() {
return speciesList;
}
public LiveData<List<DropdownDTO>> getBreedList() {
return breedList;
}
public LiveData<Boolean> getIsLoading() {
return isLoading;
}
public void setLoading(boolean loading) {
isLoading.setValue(loading);
}
// Enables/disables customer and store spinners based on the selected status
private void applyStatusRules(ViewState state, boolean clearInvalidSelections) { private void applyStatusRules(ViewState state, boolean clearInvalidSelections) {
if (STATUS_AVAILABLE.equalsIgnoreCase(state.selectedStatus)) { if (STATUS_AVAILABLE.equalsIgnoreCase(state.selectedStatus)) {
state.isCustomerEnabled = false; state.isCustomerEnabled = false;
@@ -292,10 +267,12 @@ public class PetDetailViewModel extends ViewModel {
return; return;
} }
// Adopted and Pending: both customer and store required
state.isCustomerEnabled = true; state.isCustomerEnabled = true;
state.isStoreEnabled = true; state.isStoreEnabled = true;
} }
// Normalizes any status string to one of the four known constants
private String normalizeStatus(String status) { private String normalizeStatus(String status) {
if (status == null) return STATUS_AVAILABLE; if (status == null) return STATUS_AVAILABLE;
String normalized = status.trim(); String normalized = status.trim();
@@ -305,6 +282,7 @@ public class PetDetailViewModel extends ViewModel {
return STATUS_AVAILABLE; return STATUS_AVAILABLE;
} }
// Mutates the current ViewState and re-posts it to trigger UI updates
private void updateViewState(Action<ViewState> action) { private void updateViewState(Action<ViewState> action) {
ViewState current = viewState.getValue(); ViewState current = viewState.getValue();
if (current != null) { if (current != null) {
@@ -317,6 +295,7 @@ public class PetDetailViewModel extends ViewModel {
void run(T target); void run(T target);
} }
// Observes a LiveData exactly once, removing itself after the first non-loading result
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) { private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
liveData.observeForever(new Observer<Resource<T>>() { liveData.observeForever(new Observer<Resource<T>>() {
@Override @Override
@@ -329,6 +308,7 @@ public class PetDetailViewModel extends ViewModel {
}); });
} }
// Holds all UI state for the pet detail screen
public static class ViewState { public static class ViewState {
public boolean isEditing = false; public boolean isEditing = false;
public boolean isDeleteVisible = false; public boolean isDeleteVisible = false;
@@ -346,4 +326,4 @@ public class PetDetailViewModel extends ViewModel {
public Long selectedCustomerId = null; public Long selectedCustomerId = null;
public Long selectedStoreId = null; public Long selectedStoreId = null;
} }
} }