Attachments to Chat #162
@@ -1,10 +1,12 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.os.Bundle;
|
||||
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;
|
||||
@@ -12,15 +14,15 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.example.petstoremobile.databinding.FragmentAppointmentDetailBinding;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.AppointmentViewModel;
|
||||
|
||||
import java.util.*;
|
||||
import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@@ -38,24 +40,33 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
private long preselectedStoreId = -1;
|
||||
private long preselectedStaffId = -1;
|
||||
|
||||
private final Integer[] HOURS = {9,10,11,12,13,14,15,16,17};
|
||||
private final Integer[] MINUTES = {0,15,30,45};
|
||||
private final Integer[] HOURS = {9, 10, 11, 12, 13, 14, 15, 16, 17};
|
||||
private final Integer[] MINUTES = {0, 15, 30, 45};
|
||||
|
||||
private AppointmentViewModel appointmentViewModel;
|
||||
private AppointmentDetailViewModel appointmentViewModel;
|
||||
private boolean isUpdatingUI = false;
|
||||
|
||||
/**
|
||||
* Called when the fragment is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
appointmentViewModel = new ViewModelProvider(this).get(AppointmentViewModel.class);
|
||||
appointmentViewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the view hierarchy with the fragment.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentAppointmentDetailBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called immediately after onCreateView has returned.
|
||||
*/
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@@ -70,6 +81,9 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
binding.btnDeleteAppointment.setOnClickListener(v -> confirmDelete());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the view previously created by onCreateView has been detached.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -77,7 +91,7 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the adapters for spinners.
|
||||
* Configures the adapters and listeners for all spinners.
|
||||
*/
|
||||
private void setupSpinners() {
|
||||
//Status Spinner is empty by default the date determines whats in here
|
||||
@@ -86,9 +100,9 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
// Set up hour and minute spinners
|
||||
String[] hours = new String[HOURS.length];
|
||||
for (int i = 0; i < HOURS.length; i++)
|
||||
hours[i] = String.format("%02d:00", HOURS[i]);
|
||||
hours[i] = DateTimeUtils.formatTime(HOURS[i], 0);
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerHour, hours);
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00","15","30","45"});
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00", "15", "30", "45"});
|
||||
|
||||
// Pet and Staff spinners disabled by until parent selection
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerPet, binding.spinnerStaff);
|
||||
@@ -114,42 +128,24 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
});
|
||||
|
||||
// Listeners for other selections
|
||||
binding.spinnerService.setOnItemSelectedListener(new OnIndexSelected(p -> appointmentViewModel.onServiceSelected(p)));
|
||||
binding.spinnerPet.setOnItemSelectedListener(new OnIndexSelected(p -> appointmentViewModel.onPetSelected(p)));
|
||||
binding.spinnerStaff.setOnItemSelectedListener(new OnIndexSelected(p -> appointmentViewModel.onStaffSelected(p)));
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerService, p -> appointmentViewModel.onServiceSelected(p));
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPet, p -> appointmentViewModel.onPetSelected(p));
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerStaff, p -> appointmentViewModel.onStaffSelected(p));
|
||||
|
||||
// Listeners for time changes
|
||||
binding.spinnerHour.setOnItemSelectedListener(new OnIndexSelected(p -> notifyDateTimeStatusChange()));
|
||||
binding.spinnerMinute.setOnItemSelectedListener(new OnIndexSelected(p -> notifyDateTimeStatusChange()));
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerHour, p -> notifyDateTimeStatusChange());
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerMinute, p -> notifyDateTimeStatusChange());
|
||||
|
||||
// Listener to notify ViewModel of status selection
|
||||
binding.spinnerAppointmentStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
notifyDateTimeStatusChange();
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the date picker dialog for the appointment date field.
|
||||
*/
|
||||
private void setupDatePicker() {
|
||||
binding.etAppointmentDate.setOnClickListener(v -> {
|
||||
Calendar c = Calendar.getInstance();
|
||||
DatePickerDialog d = new DatePickerDialog(requireContext(),
|
||||
(dp,y,m,d1) -> {
|
||||
String selectedDate = String.format("%04d-%02d-%02d", y, m+1, d1);
|
||||
binding.etAppointmentDate.setText(selectedDate);
|
||||
notifyDateTimeStatusChange();
|
||||
},
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH));
|
||||
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
||||
d.show();
|
||||
});
|
||||
binding.etAppointmentDate.setOnClickListener(v ->
|
||||
UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,37 +174,27 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
/**
|
||||
* Applies the ViewState provided by the ViewModel to the UI components.
|
||||
*/
|
||||
private void applyViewState(AppointmentViewModel.ViewState state) {
|
||||
private void applyViewState(AppointmentDetailViewModel.ViewState state) {
|
||||
isUpdatingUI = true;
|
||||
|
||||
// Mode specific UI
|
||||
binding.tvApptMode.setText(state.isEditing ? "Edit Appointment" : "Add Appointment");
|
||||
binding.tvAppointmentId.setText("ID: " + appointmentViewModel.getAppointmentId());
|
||||
binding.tvAppointmentId.setText(DateTimeUtils.formatId(appointmentViewModel.getAppointmentId()));
|
||||
binding.tvAppointmentId.setVisibility(state.isEditing ? View.VISIBLE : View.GONE);
|
||||
binding.btnDeleteAppointment.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||
binding.btnSaveAppointment.setVisibility(state.isSaveVisible ? View.VISIBLE : View.GONE);
|
||||
|
||||
// Enabling/Disabling Views
|
||||
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerCustomer);
|
||||
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerStore);
|
||||
UIUtils.setViewsEnabled(state.isPetEnabled, binding.spinnerPet);
|
||||
UIUtils.setViewsEnabled(state.isServiceEnabled, binding.spinnerService);
|
||||
UIUtils.setViewsEnabled(state.isStaffEnabled, binding.spinnerStaff);
|
||||
UIUtils.setViewsEnabled(state.isDateEnabled, binding.etAppointmentDate);
|
||||
UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerHour, binding.spinnerMinute);
|
||||
// Enabling/Disabling Views and Labels
|
||||
UIUtils.setFieldEnabled(state.isCustomerEnabled, binding.spinnerCustomer, binding.tvLabelCustomer);
|
||||
UIUtils.setFieldEnabled(state.isStoreEnabled, binding.spinnerStore, binding.tvLabelStore);
|
||||
UIUtils.setFieldEnabled(state.isPetEnabled, binding.spinnerPet, binding.tvLabelPet);
|
||||
UIUtils.setFieldEnabled(state.isServiceEnabled, binding.spinnerService, binding.tvLabelService);
|
||||
UIUtils.setFieldEnabled(state.isStaffEnabled, binding.spinnerStaff, binding.tvLabelStaff);
|
||||
UIUtils.setFieldEnabled(state.isDateEnabled, binding.etAppointmentDate, binding.tvLabelDate);
|
||||
UIUtils.setFieldEnabled(state.isTimeEnabled, binding.spinnerHour, binding.tvLabelTime);
|
||||
UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerMinute);
|
||||
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAppointmentStatus);
|
||||
|
||||
// Alpha for disabled look
|
||||
float alpha = 1.0f;
|
||||
float disabledAlpha = 0.5f;
|
||||
UIUtils.setViewsAlpha(state.isCustomerEnabled ? alpha : disabledAlpha, binding.tvLabelCustomer);
|
||||
UIUtils.setViewsAlpha(state.isStoreEnabled ? alpha : disabledAlpha, binding.tvLabelStore);
|
||||
UIUtils.setViewsAlpha(state.isPetEnabled ? alpha : disabledAlpha, binding.tvLabelPet);
|
||||
UIUtils.setViewsAlpha(state.isServiceEnabled ? alpha : disabledAlpha, binding.tvLabelService);
|
||||
UIUtils.setViewsAlpha(state.isStaffEnabled ? alpha : disabledAlpha, binding.tvLabelStaff);
|
||||
UIUtils.setViewsAlpha(state.isDateEnabled ? alpha : disabledAlpha, binding.tvLabelDate);
|
||||
UIUtils.setViewsAlpha(state.isTimeEnabled ? alpha : disabledAlpha, binding.tvLabelTime);
|
||||
|
||||
// Update status options
|
||||
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
|
||||
String current = selected != null ? selected.toString() : "";
|
||||
@@ -218,6 +204,9 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
isUpdatingUI = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the ViewModel that the date, time, or status has changed.
|
||||
*/
|
||||
private void notifyDateTimeStatusChange() {
|
||||
if (isUpdatingUI) return;
|
||||
|
||||
@@ -282,7 +271,7 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
|
||||
appointmentViewModel.saveAppointment(date, time, status).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
AppointmentViewModel.ViewState state = appointmentViewModel.getViewState().getValue();
|
||||
AppointmentDetailViewModel.ViewState state = appointmentViewModel.getViewState().getValue();
|
||||
String message = (state != null && state.isEditing) ? "Updated" : "Saved";
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
@@ -296,24 +285,19 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
* Validates that all required fields are selected.
|
||||
*/
|
||||
private boolean validateRequiredFields() {
|
||||
if (binding.spinnerCustomer.getSelectedItemPosition() == 0) return showToast("Select a customer");
|
||||
if (binding.spinnerStore.getSelectedItemPosition() == 0) return showToast("Select a store");
|
||||
if (binding.spinnerPet.getSelectedItemPosition() == 0) return showToast("Select a pet");
|
||||
if (binding.spinnerService.getSelectedItemPosition() == 0) return showToast("Select a service");
|
||||
if (binding.etAppointmentDate.getText().toString().trim().isEmpty()) return showToast("Select a date");
|
||||
if (binding.spinnerCustomer.getSelectedItemPosition() == 0) return UIUtils.showToast(getContext(), "Select a customer");
|
||||
if (binding.spinnerStore.getSelectedItemPosition() == 0) return UIUtils.showToast(getContext(), "Select a store");
|
||||
if (binding.spinnerPet.getSelectedItemPosition() == 0) return UIUtils.showToast(getContext(), "Select a pet");
|
||||
if (binding.spinnerService.getSelectedItemPosition() == 0) return UIUtils.showToast(getContext(), "Select a service");
|
||||
if (binding.etAppointmentDate.getText().toString().trim().isEmpty()) return UIUtils.showToast(getContext(), "Select a date");
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean showToast(String msg) {
|
||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a time string from the hour and minute spinners.
|
||||
*/
|
||||
private String buildTimeString() {
|
||||
return String.format("%02d:%02d", HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
|
||||
return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,17 +342,7 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
private void parseAndSetTimeSpinners(String time) {
|
||||
int[] parsedTime = DateTimeUtils.parseTimeString(time);
|
||||
if (parsedTime == null) return;
|
||||
for (int i = 0; i < HOURS.length; i++) if (HOURS[i] == parsedTime[0]) binding.spinnerHour.setSelection(i);
|
||||
for (int i = 0; i < MINUTES.length; i++) if (MINUTES[i] == parsedTime[1]) binding.spinnerMinute.setSelection(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper listener for simple index reporting.
|
||||
*/
|
||||
private static class OnIndexSelected implements AdapterView.OnItemSelectedListener {
|
||||
private final java.util.function.Consumer<Integer> callback;
|
||||
public OnIndexSelected(java.util.function.Consumer<Integer> callback) { this.callback = callback; }
|
||||
@Override public void onItemSelected(AdapterView<?> p, View v, int pos, long id) { callback.accept(pos); }
|
||||
@Override public void onNothingSelected(AdapterView<?> p) {}
|
||||
SpinnerUtils.setSelectionByValueArray(binding.spinnerHour, HOURS, parsedTime[0]);
|
||||
SpinnerUtils.setSelectionByValueArray(binding.spinnerMinute, MINUTES, parsedTime[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.example.petstoremobile.utils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility class for date and time operations.
|
||||
@@ -78,7 +79,7 @@ public class DateTimeUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a time string and returns hour and minute indices for the spinners.
|
||||
* Parses a time string and returns hour and minute values.
|
||||
*/
|
||||
public static int[] parseTimeString(String time) {
|
||||
if (time == null || time.isEmpty()) return null;
|
||||
@@ -95,4 +96,18 @@ public class DateTimeUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an hour and minute into an HH:mm string.
|
||||
*/
|
||||
public static String formatTime(int hour, int minute) {
|
||||
return String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an ID for display (e.g., "ID: 123").
|
||||
*/
|
||||
public static String formatId(long id) {
|
||||
return String.format(Locale.getDefault(), "ID: %d", id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.example.petstoremobile.adapters.WhiteTextArrayAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
@@ -96,6 +97,13 @@ public class SpinnerUtils {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener that provides the selected index to a consumer.
|
||||
*/
|
||||
public static void setOnIndexSelectedListener(Spinner spinner, Consumer<Integer> callback) {
|
||||
spinner.setOnItemSelectedListener(new OnIndexSelected(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection of a spinner based on a string value.
|
||||
*/
|
||||
@@ -108,6 +116,19 @@ public class SpinnerUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection of a spinner based on a value within an array.
|
||||
*/
|
||||
public static <T> void setSelectionByValueArray(Spinner spinner, T[] array, T value) {
|
||||
if (spinner == null || array == null || value == null) return;
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (Objects.equals(array[i], value)) {
|
||||
spinner.setSelection(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a simple string array spinner.
|
||||
*/
|
||||
@@ -117,4 +138,23 @@ public class SpinnerUtils {
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper listener to get selected index from a spinner.
|
||||
*/
|
||||
public static class OnIndexSelected implements AdapterView.OnItemSelectedListener {
|
||||
private final Consumer<Integer> callback;
|
||||
|
||||
public OnIndexSelected(Consumer<Integer> callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
callback.accept(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.example.petstoremobile.utils;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.content.Context;
|
||||
import android.telephony.PhoneNumberFormattingTextWatcher;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
@@ -8,10 +10,14 @@ import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility class for shared UI component logic and formatting.
|
||||
*/
|
||||
@@ -32,7 +38,6 @@ public class UIUtils {
|
||||
boolean isVisible = layoutFilter.getVisibility() == View.VISIBLE;
|
||||
layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||
|
||||
// Use Android default icons or app-specific ones if available
|
||||
btnToggle.setImageResource(isVisible ?
|
||||
android.R.drawable.ic_menu_search :
|
||||
android.R.drawable.ic_menu_close_clear_cancel);
|
||||
@@ -69,7 +74,6 @@ public class UIUtils {
|
||||
|
||||
/**
|
||||
* Sets the enabled state and alpha for multiple views.
|
||||
* Alpha is set to 1.0f when enabled and 0.5f when disabled.
|
||||
*/
|
||||
public static void setViewsEnabled(boolean enabled, View... views) {
|
||||
for (View v : views) {
|
||||
@@ -80,6 +84,19 @@ public class UIUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets enabled state for a field and updates alpha for both the field and its label.
|
||||
*/
|
||||
public static void setFieldEnabled(boolean enabled, View field, View label) {
|
||||
if (field != null) {
|
||||
field.setEnabled(enabled);
|
||||
field.setAlpha(enabled ? 1.0f : 0.5f);
|
||||
}
|
||||
if (label != null) {
|
||||
label.setAlpha(enabled ? 1.0f : 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alpha for multiple views.
|
||||
*/
|
||||
@@ -90,4 +107,31 @@ public class UIUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a DatePickerDialog and sets the result to an EditText.
|
||||
*/
|
||||
public static void showDatePicker(Context context, EditText editText, Runnable onDateSet) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
DatePickerDialog d = new DatePickerDialog(context,
|
||||
(dp, y, m, d1) -> {
|
||||
String selectedDate = String.format(Locale.getDefault(), "%04d-%02d-%02d", y, m + 1, d1);
|
||||
editText.setText(selectedDate);
|
||||
if (onDateSet != null) onDateSet.run();
|
||||
},
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH));
|
||||
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
||||
d.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a toast and returns false. Useful for validation chains.
|
||||
*/
|
||||
public static boolean showToast(Context context, String msg) {
|
||||
if (context != null) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
/**
|
||||
* ViewModel for managing appointment details and form state.
|
||||
*/
|
||||
@HiltViewModel
|
||||
public class AppointmentDetailViewModel extends ViewModel {
|
||||
private final AppointmentRepository repository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
private final PetRepository petRepository;
|
||||
private final ServiceRepository serviceRepository;
|
||||
|
||||
private final MutableLiveData<List<DropdownDTO>> customers = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> stores = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<ServiceDTO>> services = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> customerPets = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> storeEmployees = new MutableLiveData<>();
|
||||
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||
|
||||
private long appointmentId = -1;
|
||||
private Long currentCustomerId;
|
||||
private Long currentStoreId;
|
||||
private Long currentPetId;
|
||||
private Long currentServiceId;
|
||||
private Long currentStaffId;
|
||||
|
||||
/**
|
||||
* Constructor for AppointmentDetailViewModel.
|
||||
*/
|
||||
@Inject
|
||||
public AppointmentDetailViewModel(
|
||||
AppointmentRepository repository,
|
||||
CustomerRepository customerRepository,
|
||||
StoreRepository storeRepository,
|
||||
PetRepository petRepository,
|
||||
ServiceRepository serviceRepository) {
|
||||
this.repository = repository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
this.petRepository = petRepository;
|
||||
this.serviceRepository = serviceRepository;
|
||||
}
|
||||
|
||||
// Initial Data Loading
|
||||
|
||||
/**
|
||||
* Loads initial dropdown data for customers, stores, and services.
|
||||
*/
|
||||
public void loadInitialFormData() {
|
||||
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) customers.setValue(r.data);
|
||||
});
|
||||
storeRepository.getStoreDropdowns().observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) stores.setValue(r.data);
|
||||
});
|
||||
serviceRepository.getAllServices(0, 200, null, "serviceName").observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS && r.data != null) services.setValue(r.data.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
// LiveData Getters
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of customers.
|
||||
*/
|
||||
public LiveData<List<DropdownDTO>> getCustomers() { return customers; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of stores.
|
||||
*/
|
||||
public LiveData<List<DropdownDTO>> getStores() { return stores; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of services.
|
||||
*/
|
||||
public LiveData<List<ServiceDTO>> getServices() { return services; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of pets for the current customer.
|
||||
*/
|
||||
public LiveData<List<DropdownDTO>> getCustomerPets() { return customerPets; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of employees for the current store.
|
||||
*/
|
||||
public LiveData<List<DropdownDTO>> getStoreEmployees() { return storeEmployees; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the view state.
|
||||
*/
|
||||
public LiveData<ViewState> getViewState() { return viewState; }
|
||||
|
||||
//State Getters
|
||||
|
||||
/**
|
||||
* Returns the current appointment ID.
|
||||
*/
|
||||
public long getAppointmentId() { return appointmentId; }
|
||||
|
||||
/**
|
||||
* Sets the current appointment ID and updates the mode.
|
||||
*/
|
||||
public void setAppointmentId(long id) {
|
||||
this.appointmentId = id;
|
||||
initMode(id != -1);
|
||||
}
|
||||
|
||||
// Selection Handlers for spinners
|
||||
|
||||
/**
|
||||
* Handles customer selection and loads their pets.
|
||||
*/
|
||||
public void onCustomerSelected(int position) {
|
||||
List<DropdownDTO> list = customers.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
currentCustomerId = list.get(position - 1).getId();
|
||||
loadPetsForCustomer(currentCustomerId);
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = currentCustomerId;
|
||||
s.isPetEnabled = !s.isEditing;
|
||||
});
|
||||
} else {
|
||||
currentCustomerId = null;
|
||||
customerPets.setValue(new ArrayList<>());
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = null;
|
||||
s.isPetEnabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles store selection and loads its employees.
|
||||
*/
|
||||
public void onStoreSelected(int position) {
|
||||
List<DropdownDTO> list = stores.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
currentStoreId = list.get(position - 1).getId();
|
||||
loadEmployeesForStore(currentStoreId);
|
||||
updateViewState(s -> {
|
||||
s.selectedStoreId = currentStoreId;
|
||||
s.isStaffEnabled = !s.isPast;
|
||||
});
|
||||
} else {
|
||||
currentStoreId = null;
|
||||
storeEmployees.setValue(new ArrayList<>());
|
||||
updateViewState(s -> {
|
||||
s.selectedStoreId = null;
|
||||
s.isStaffEnabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles service selection.
|
||||
*/
|
||||
public void onServiceSelected(int position) {
|
||||
List<ServiceDTO> list = services.getValue();
|
||||
currentServiceId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getServiceId() : null;
|
||||
updateViewState(s -> s.selectedServiceId = currentServiceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles pet selection.
|
||||
*/
|
||||
public void onPetSelected(int position) {
|
||||
List<DropdownDTO> list = customerPets.getValue();
|
||||
currentPetId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getId() : null;
|
||||
updateViewState(s -> s.selectedPetId = currentPetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles staff selection.
|
||||
*/
|
||||
public void onStaffSelected(int position) {
|
||||
List<DropdownDTO> list = storeEmployees.getValue();
|
||||
currentStaffId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getId() : null;
|
||||
updateViewState(s -> s.selectedStaffId = currentStaffId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of pets for a specific customer.
|
||||
*/
|
||||
private void loadPetsForCustomer(Long customerId) {
|
||||
petRepository.getCustomerPets(customerId).observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) customerPets.setValue(r.data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of employees for a specific store.
|
||||
*/
|
||||
private void loadEmployeesForStore(Long storeId) {
|
||||
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) storeEmployees.setValue(r.data);
|
||||
});
|
||||
}
|
||||
|
||||
// Appointment Detail CRUD
|
||||
|
||||
/**
|
||||
* Fetches appointment details and populates internal state.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> loadAppointment() {
|
||||
MutableLiveData<Resource<AppointmentDTO>> result = new MutableLiveData<>();
|
||||
repository.getAppointmentById(appointmentId).observeForever(resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
AppointmentDTO a = resource.data;
|
||||
currentCustomerId = a.getCustomerId();
|
||||
currentStoreId = a.getStoreId();
|
||||
currentPetId = a.getPetId();
|
||||
currentServiceId = a.getServiceId();
|
||||
currentStaffId = a.getEmployeeId();
|
||||
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = currentCustomerId;
|
||||
s.selectedStoreId = currentStoreId;
|
||||
s.selectedPetId = currentPetId;
|
||||
s.selectedServiceId = currentServiceId;
|
||||
s.selectedStaffId = currentStaffId;
|
||||
});
|
||||
|
||||
if (currentCustomerId != null) loadPetsForCustomer(currentCustomerId);
|
||||
if (currentStoreId != null) loadEmployeesForStore(currentStoreId);
|
||||
}
|
||||
result.setValue(resource);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the appointment by building the DTO from tracked state.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> saveAppointment(String date, String time, String status) {
|
||||
AppointmentDTO dto = new AppointmentDTO(currentCustomerId, currentStoreId, currentServiceId, currentStaffId, date, time, status, currentPetId);
|
||||
if (appointmentId != -1) {
|
||||
return repository.updateAppointment(appointmentId, dto);
|
||||
} else {
|
||||
return repository.createAppointment(dto);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current appointment.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteAppointment() {
|
||||
return repository.deleteAppointment(appointmentId);
|
||||
}
|
||||
|
||||
// UI Logic
|
||||
|
||||
/**
|
||||
* Updates the UI state when date, time, or status changes.
|
||||
*/
|
||||
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
||||
updateViewState(s -> {
|
||||
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
||||
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
||||
boolean isCancelled = "Cancelled".equalsIgnoreCase(currentStatus);
|
||||
|
||||
if (isCancelled) {
|
||||
s.isPast = true;
|
||||
setAllFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = false;
|
||||
s.isSaveVisible = false;
|
||||
} else if (isPast) {
|
||||
s.isPast = true;
|
||||
setAllFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = true;
|
||||
} else {
|
||||
s.isPast = false;
|
||||
if (!s.isEditing) {
|
||||
s.isCustomerEnabled = true;
|
||||
s.isStoreEnabled = true;
|
||||
s.isServiceEnabled = true;
|
||||
s.isPetEnabled = currentCustomerId != null;
|
||||
}
|
||||
s.isDateEnabled = true;
|
||||
s.isTimeEnabled = true;
|
||||
s.isStatusEnabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates available appointment statuses based on the current context.
|
||||
*/
|
||||
private String[] calculateAvailableStatuses(boolean isEditing, String date, String currentTime, String currentStatus) {
|
||||
if (!isEditing) return new String[]{"Booked"};
|
||||
if (date == null || date.isEmpty()) return new String[]{};
|
||||
if ("Cancelled".equalsIgnoreCase(currentStatus)) return new String[]{"Cancelled"};
|
||||
if (DateTimeUtils.isDateTimeInPast(date, currentTime)) return new String[]{"Completed", "Missed"};
|
||||
return new String[]{"Booked", "Cancelled"};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to enable or disable all fields.
|
||||
*/
|
||||
private void setAllFieldsEnabled(ViewState s, boolean enabled) {
|
||||
s.isCustomerEnabled = enabled;
|
||||
s.isStoreEnabled = enabled;
|
||||
s.isPetEnabled = enabled;
|
||||
s.isServiceEnabled = enabled;
|
||||
s.isStaffEnabled = enabled;
|
||||
s.isDateEnabled = enabled;
|
||||
s.isTimeEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the UI mode (Create vs Edit).
|
||||
*/
|
||||
public void initMode(boolean isEditing) {
|
||||
updateViewState(s -> {
|
||||
s.isEditing = isEditing;
|
||||
s.isDeleteVisible = isEditing;
|
||||
if (isEditing) {
|
||||
s.isCustomerEnabled = false;
|
||||
s.isStoreEnabled = false;
|
||||
s.isPetEnabled = false;
|
||||
s.isServiceEnabled = false;
|
||||
} else {
|
||||
s.isCustomerEnabled = true;
|
||||
s.isStoreEnabled = true;
|
||||
s.isServiceEnabled = true;
|
||||
s.isPetEnabled = false; // until customer selected
|
||||
s.isStaffEnabled = false; // until store selected
|
||||
s.availableStatuses = new String[]{"Booked"};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a booking is in the future.
|
||||
*/
|
||||
public boolean isValidFutureBooking(String status, String date, String time) {
|
||||
return !"BOOKED".equalsIgnoreCase(status) || !DateTimeUtils.isDateTimeInPast(date, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to update the view state and notify observers.
|
||||
*/
|
||||
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 t);
|
||||
}
|
||||
|
||||
/**
|
||||
* A Class to show the states of Appointment Detail Fragment.
|
||||
*/
|
||||
public static class ViewState {
|
||||
public boolean isPast = false;
|
||||
public boolean isEditing = false;
|
||||
public boolean isSaveVisible = true;
|
||||
public boolean isDeleteVisible = false;
|
||||
public boolean isCustomerEnabled = true;
|
||||
public boolean isStoreEnabled = true;
|
||||
public boolean isPetEnabled = false;
|
||||
public boolean isServiceEnabled = true;
|
||||
public boolean isStaffEnabled = false;
|
||||
public boolean isDateEnabled = true;
|
||||
public boolean isTimeEnabled = true;
|
||||
public boolean isStatusEnabled = true;
|
||||
public String[] availableStatuses = new String[]{};
|
||||
|
||||
// Selected IDs
|
||||
public Long selectedCustomerId = null;
|
||||
public Long selectedStoreId = null;
|
||||
public Long selectedPetId = null;
|
||||
public Long selectedServiceId = null;
|
||||
public Long selectedStaffId = null;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,14 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -27,38 +18,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
@HiltViewModel
|
||||
public class AppointmentViewModel extends ViewModel {
|
||||
private final AppointmentRepository repository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
private final PetRepository petRepository;
|
||||
private final ServiceRepository serviceRepository;
|
||||
|
||||
private final MutableLiveData<List<DropdownDTO>> customers = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> stores = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<ServiceDTO>> services = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> customerPets = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> storeEmployees = new MutableLiveData<>();
|
||||
|
||||
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||
|
||||
private long appointmentId = -1;
|
||||
private Long currentCustomerId;
|
||||
private Long currentStoreId;
|
||||
private Long currentPetId;
|
||||
private Long currentServiceId;
|
||||
private Long currentStaffId;
|
||||
|
||||
@Inject
|
||||
public AppointmentViewModel(
|
||||
AppointmentRepository repository,
|
||||
CustomerRepository customerRepository,
|
||||
StoreRepository storeRepository,
|
||||
PetRepository petRepository,
|
||||
ServiceRepository serviceRepository) {
|
||||
public AppointmentViewModel(AppointmentRepository repository) {
|
||||
this.repository = repository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
this.petRepository = petRepository;
|
||||
this.serviceRepository = serviceRepository;
|
||||
}
|
||||
|
||||
// API CRUD
|
||||
@@ -70,307 +33,10 @@ public class AppointmentViewModel extends ViewModel {
|
||||
return repository.getAllAppointments(page, size, query, status, storeId, date, employeeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single appointment by its ID.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> getAppointmentById(Long id) {
|
||||
return repository.getAppointmentById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new appointment.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> createAppointment(AppointmentDTO appointment) {
|
||||
return repository.createAppointment(appointment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing appointment record by ID.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> updateAppointment(Long id, AppointmentDTO appointment) {
|
||||
return repository.updateAppointment(id, appointment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an appointment record by ID.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteAppointment(Long id) {
|
||||
return repository.deleteAppointment(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple appointment records.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeleteAppointments(List<String> ids) {
|
||||
return repository.bulkDeleteAppointments(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
// Initial Data Loading
|
||||
|
||||
/**
|
||||
* Loads initial dropdown data for customers, stores, and services.
|
||||
*/
|
||||
public void loadInitialFormData() {
|
||||
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) customers.setValue(r.data);
|
||||
});
|
||||
storeRepository.getStoreDropdowns().observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) stores.setValue(r.data);
|
||||
});
|
||||
serviceRepository.getAllServices(0, 200, null, "serviceName").observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS && r.data != null) services.setValue(r.data.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
// LiveData Getters
|
||||
|
||||
public LiveData<List<DropdownDTO>> getCustomers() { return customers; }
|
||||
public LiveData<List<DropdownDTO>> getStores() { return stores; }
|
||||
public LiveData<List<ServiceDTO>> getServices() { return services; }
|
||||
public LiveData<List<DropdownDTO>> getCustomerPets() { return customerPets; }
|
||||
public LiveData<List<DropdownDTO>> getStoreEmployees() { return storeEmployees; }
|
||||
public LiveData<ViewState> getViewState() { return viewState; }
|
||||
|
||||
//State Getters
|
||||
|
||||
public long getAppointmentId() { return appointmentId; }
|
||||
|
||||
/**
|
||||
* Sets the current appointment ID and updates the mode.
|
||||
*/
|
||||
public void setAppointmentId(long id) {
|
||||
this.appointmentId = id;
|
||||
initMode(id != -1);
|
||||
}
|
||||
|
||||
// Selection Handlers for spinners
|
||||
|
||||
public void onCustomerSelected(int position) {
|
||||
List<DropdownDTO> list = customers.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
currentCustomerId = list.get(position - 1).getId();
|
||||
loadPetsForCustomer(currentCustomerId);
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = currentCustomerId;
|
||||
s.isPetEnabled = !s.isEditing;
|
||||
});
|
||||
} else {
|
||||
currentCustomerId = null;
|
||||
customerPets.setValue(new ArrayList<>());
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = null;
|
||||
s.isPetEnabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onStoreSelected(int position) {
|
||||
List<DropdownDTO> list = stores.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
currentStoreId = list.get(position - 1).getId();
|
||||
loadEmployeesForStore(currentStoreId);
|
||||
updateViewState(s -> {
|
||||
s.selectedStoreId = currentStoreId;
|
||||
s.isStaffEnabled = !s.isPast;
|
||||
});
|
||||
} else {
|
||||
currentStoreId = null;
|
||||
storeEmployees.setValue(new ArrayList<>());
|
||||
updateViewState(s -> {
|
||||
s.selectedStoreId = null;
|
||||
s.isStaffEnabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onServiceSelected(int position) {
|
||||
List<ServiceDTO> list = services.getValue();
|
||||
currentServiceId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getServiceId() : null;
|
||||
updateViewState(s -> s.selectedServiceId = currentServiceId);
|
||||
}
|
||||
|
||||
public void onPetSelected(int position) {
|
||||
List<DropdownDTO> list = customerPets.getValue();
|
||||
currentPetId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getId() : null;
|
||||
updateViewState(s -> s.selectedPetId = currentPetId);
|
||||
}
|
||||
|
||||
public void onStaffSelected(int position) {
|
||||
List<DropdownDTO> list = storeEmployees.getValue();
|
||||
currentStaffId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getId() : null;
|
||||
updateViewState(s -> s.selectedStaffId = currentStaffId);
|
||||
}
|
||||
|
||||
private void loadPetsForCustomer(Long customerId) {
|
||||
petRepository.getCustomerPets(customerId).observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) customerPets.setValue(r.data);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadEmployeesForStore(Long storeId) {
|
||||
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) storeEmployees.setValue(r.data);
|
||||
});
|
||||
}
|
||||
|
||||
// Appointment Detail CRUD
|
||||
|
||||
/**
|
||||
* Fetches appointment details and populates internal state.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> loadAppointment() {
|
||||
MutableLiveData<Resource<AppointmentDTO>> result = new MutableLiveData<>();
|
||||
repository.getAppointmentById(appointmentId).observeForever(resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
AppointmentDTO a = resource.data;
|
||||
currentCustomerId = a.getCustomerId();
|
||||
currentStoreId = a.getStoreId();
|
||||
currentPetId = a.getPetId();
|
||||
currentServiceId = a.getServiceId();
|
||||
currentStaffId = a.getEmployeeId();
|
||||
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = currentCustomerId;
|
||||
s.selectedStoreId = currentStoreId;
|
||||
s.selectedPetId = currentPetId;
|
||||
s.selectedServiceId = currentServiceId;
|
||||
s.selectedStaffId = currentStaffId;
|
||||
});
|
||||
|
||||
if (currentCustomerId != null) loadPetsForCustomer(currentCustomerId);
|
||||
if (currentStoreId != null) loadEmployeesForStore(currentStoreId);
|
||||
}
|
||||
result.setValue(resource);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the appointment by building the DTO from tracked state.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> saveAppointment(String date, String time, String status) {
|
||||
AppointmentDTO dto = new AppointmentDTO(currentCustomerId, currentStoreId, currentServiceId, currentStaffId, date, time, status, currentPetId);
|
||||
if (appointmentId != -1) {
|
||||
return repository.updateAppointment(appointmentId, dto);
|
||||
} else {
|
||||
return repository.createAppointment(dto);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current appointment.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteAppointment() {
|
||||
return repository.deleteAppointment(appointmentId);
|
||||
}
|
||||
|
||||
// --- UI Logic ---
|
||||
|
||||
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
||||
updateViewState(s -> {
|
||||
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
||||
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
||||
boolean isCancelled = "Cancelled".equalsIgnoreCase(currentStatus);
|
||||
|
||||
if (isCancelled) {
|
||||
s.isPast = true;
|
||||
setAllFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = false;
|
||||
s.isSaveVisible = false;
|
||||
} else if (isPast) {
|
||||
s.isPast = true;
|
||||
setAllFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = true;
|
||||
} else {
|
||||
s.isPast = false;
|
||||
if (!s.isEditing) {
|
||||
s.isCustomerEnabled = true;
|
||||
s.isStoreEnabled = true;
|
||||
s.isServiceEnabled = true;
|
||||
s.isPetEnabled = currentCustomerId != null;
|
||||
}
|
||||
s.isDateEnabled = true;
|
||||
s.isTimeEnabled = true;
|
||||
s.isStatusEnabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String[] calculateAvailableStatuses(boolean isEditing, String date, String currentTime, String currentStatus) {
|
||||
if (!isEditing) return new String[]{"Booked"};
|
||||
if (date == null || date.isEmpty()) return new String[]{};
|
||||
if ("Cancelled".equalsIgnoreCase(currentStatus)) return new String[]{"Cancelled"};
|
||||
if (DateTimeUtils.isDateTimeInPast(date, currentTime)) return new String[]{"Completed", "Missed"};
|
||||
return new String[]{"Booked", "Cancelled"};
|
||||
}
|
||||
|
||||
private void setAllFieldsEnabled(ViewState s, boolean enabled) {
|
||||
s.isCustomerEnabled = enabled;
|
||||
s.isStoreEnabled = enabled;
|
||||
s.isPetEnabled = enabled;
|
||||
s.isServiceEnabled = enabled;
|
||||
s.isStaffEnabled = enabled;
|
||||
s.isDateEnabled = enabled;
|
||||
s.isTimeEnabled = enabled;
|
||||
}
|
||||
|
||||
public void initMode(boolean isEditing) {
|
||||
updateViewState(s -> {
|
||||
s.isEditing = isEditing;
|
||||
s.isDeleteVisible = isEditing;
|
||||
if (isEditing) {
|
||||
s.isCustomerEnabled = false;
|
||||
s.isStoreEnabled = false;
|
||||
s.isPetEnabled = false;
|
||||
s.isServiceEnabled = false;
|
||||
} else {
|
||||
s.isCustomerEnabled = true;
|
||||
s.isStoreEnabled = true;
|
||||
s.isServiceEnabled = true;
|
||||
s.isPetEnabled = false; // until customer selected
|
||||
s.isStaffEnabled = false; // until store selected
|
||||
s.availableStatuses = new String[]{"Booked"};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isValidFutureBooking(String status, String date, String time) {
|
||||
return !"BOOKED".equalsIgnoreCase(status) || !DateTimeUtils.isDateTimeInPast(date, time);
|
||||
}
|
||||
|
||||
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 t); }
|
||||
|
||||
/**
|
||||
* A Class to show the states of Appointment Detail Fragment.
|
||||
*/
|
||||
public static class ViewState {
|
||||
public boolean isPast = false;
|
||||
public boolean isEditing = false;
|
||||
public boolean isSaveVisible = true;
|
||||
public boolean isDeleteVisible = false;
|
||||
public boolean isCustomerEnabled = true;
|
||||
public boolean isStoreEnabled = true;
|
||||
public boolean isPetEnabled = false;
|
||||
public boolean isServiceEnabled = true;
|
||||
public boolean isStaffEnabled = false;
|
||||
public boolean isDateEnabled = true;
|
||||
public boolean isTimeEnabled = true;
|
||||
public boolean isStatusEnabled = true;
|
||||
public String[] availableStatuses = new String[]{};
|
||||
|
||||
// Selected IDs
|
||||
public Long selectedCustomerId = null;
|
||||
public Long selectedStoreId = null;
|
||||
public Long selectedPetId = null;
|
||||
public Long selectedServiceId = null;
|
||||
public Long selectedStaffId = null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user