From 5fa9cfd5d6328cc4bf5949aa288aeebb81e526e8 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:37:43 -0600 Subject: [PATCH] added calendar view to appointments - NOTE: may have to change appointments abit after backend is updated --- android/app/build.gradle.kts | 2 + .../listfragments/AppointmentFragment.java | 113 +++++++++++++++--- .../petstoremobile/utils/EventDecorator.java | 30 +++++ .../main/res/layout/fragment_appointment.xml | 22 +++- android/gradle.properties | 2 + 5 files changed, 149 insertions(+), 20 deletions(-) create mode 100644 android/app/src/main/java/com/example/petstoremobile/utils/EventDecorator.java diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e3955e2f..a6d27404 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -82,6 +82,8 @@ dependencies { implementation("com.github.bumptech.glide:glide:4.16.0") annotationProcessor("com.github.bumptech.glide:compiler:4.16.0") + implementation("com.github.prolificinteractive:material-calendarview:2.0.1") + testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java index f8aa734f..49da714c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java @@ -1,5 +1,6 @@ package com.example.petstoremobile.fragments.listfragments; +import android.graphics.Color; import android.os.Bundle; import androidx.annotation.NonNull; @@ -29,10 +30,21 @@ import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.PetDTO; import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.listfragments.detailfragments.AppointmentDetailFragment; +import com.example.petstoremobile.utils.EventDecorator; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.prolificinteractive.materialcalendarview.CalendarDay; +import com.prolificinteractive.materialcalendarview.CalendarMode; +import com.prolificinteractive.materialcalendarview.MaterialCalendarView; +import com.prolificinteractive.materialcalendarview.OnDateSelectedListener; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import retrofit2.Call; import retrofit2.Callback; @@ -50,6 +62,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. private SwipeRefreshLayout swipeRefreshLayout; private EditText etSearch; private ImageButton hamburger; + private ImageButton btnToggleCalendarMode; + private MaterialCalendarView calendarView; + private CalendarDay selectedCalendarDay; + private boolean isMonthMode = false; + private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -58,10 +75,13 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. api = RetrofitClient.getAppointmentApi(requireContext()); hamburger = view.findViewById(R.id.btnHamburger); + calendarView = view.findViewById(R.id.calendarView); + btnToggleCalendarMode = view.findViewById(R.id.btnToggleCalendarMode); setupRecyclerView(view); setupSearch(view); setupSwipeRefresh(view); + setupCalendar(); loadAppointmentData(); loadPets(); loadServices(); @@ -76,9 +96,60 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. listFragment.openDrawer(); }); + btnToggleCalendarMode.setOnClickListener(v -> toggleCalendarMode()); + return view; } + // Toggle Calendar Mode from week to month and other way around + private void toggleCalendarMode() { + isMonthMode = !isMonthMode; + calendarView.state().edit() + .setCalendarDisplayMode(isMonthMode ? CalendarMode.MONTHS : CalendarMode.WEEKS) + .commit(); + } + + private void setupCalendar() { + calendarView.setOnDateChangedListener(new OnDateSelectedListener() { + @Override + public void onDateSelected(@NonNull MaterialCalendarView widget, @NonNull CalendarDay date, boolean selected) { + if (selected) { + if (date.equals(selectedCalendarDay)) { + selectedCalendarDay = null; + calendarView.clearSelection(); + } else { + selectedCalendarDay = date; + } + } else { + selectedCalendarDay = null; + } + filterAppointments(etSearch.getText().toString()); + } + }); + } + + //Set indicators for dates with appointments on the calendar + private void updateCalendarDecorators() { + HashSet datesWithAppointments = new HashSet<>(); + for (AppointmentDTO appointment : appointmentList) { + try { + //Get the appointment date + Date date = dateFormat.parse(appointment.getAppointmentDate()); + //if the date is not null, add it to the hashset + if (date != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + datesWithAppointments.add(CalendarDay.from(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH))); + } + } catch (ParseException e) { + Log.e("AppointmentFragment", "Error parsing date: " + appointment.getAppointmentDate()); + } + } + //update the indicators to the calendar + calendarView.removeDecorators(); + calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments)); + } + private void setupSearch(View view) { etSearch = view.findViewById(R.id.etSearchAppointment); etSearch.addTextChangedListener(new TextWatcher() { @@ -99,16 +170,25 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. private void filterAppointments(String query) { filteredList.clear(); - if (query.isEmpty()) { - filteredList.addAll(appointmentList); - } else { - String lower = query.toLowerCase(); - for (AppointmentDTO a : appointmentList) { - if ((a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lower)) - || (a.getServiceType() != null && a.getServiceType().toLowerCase().contains(lower)) - || (a.getPetName() != null && a.getPetName().toLowerCase().contains(lower))) { - filteredList.add(a); - } + String lowerQuery = query.toLowerCase(); + + String selectedDateString = null; + if (selectedCalendarDay != null) { + selectedDateString = String.format(Locale.getDefault(), "%04d-%02d-%02d", + selectedCalendarDay.getYear(), selectedCalendarDay.getMonth(), selectedCalendarDay.getDay()); + } + + for (AppointmentDTO a : appointmentList) { + boolean matchesSearch = query.isEmpty() || + (a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lowerQuery)) || + (a.getServiceType() != null && a.getServiceType().toLowerCase().contains(lowerQuery)) || + (a.getPetName() != null && a.getPetName().toLowerCase().contains(lowerQuery)); + + boolean matchesDate = (selectedDateString == null) || + (a.getAppointmentDate() != null && a.getAppointmentDate().equals(selectedDateString)); + + if (matchesSearch && matchesDate) { + filteredList.add(a); } } adapter.notifyDataSetChanged(); @@ -141,17 +221,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. if (lf != null) lf.loadFragment(detailFragment); } public void onAppointmentSaved(int position, AppointmentDTO appointment) { - if (position == -1) { - appointmentList.add(appointment); - } else { - appointmentList.set(position, appointment); - } - filterAppointments(etSearch.getText().toString()); + loadAppointmentData(); } public void onAppointmentDeleted(int position) { - appointmentList.remove(position); - filterAppointments(etSearch.getText().toString()); + loadAppointmentData(); } @Override @@ -162,7 +236,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. private void loadAppointmentData() { if (swipeRefreshLayout != null) swipeRefreshLayout.setRefreshing(true); - api.getAllAppointments(0, 100).enqueue(new Callback>() { + api.getAllAppointments(0, 500).enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { @@ -171,6 +245,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. if (response.isSuccessful() && response.body() != null) { appointmentList.clear(); appointmentList.addAll(response.body().getContent()); + updateCalendarDecorators(); filterAppointments(etSearch != null ? etSearch.getText().toString() : ""); } else { Log.e("AppointmentFragment", "Error: " + response.message()); diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/EventDecorator.java b/android/app/src/main/java/com/example/petstoremobile/utils/EventDecorator.java new file mode 100644 index 00000000..b58f38d4 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/utils/EventDecorator.java @@ -0,0 +1,30 @@ +package com.example.petstoremobile.utils; + +import com.prolificinteractive.materialcalendarview.CalendarDay; +import com.prolificinteractive.materialcalendarview.DayViewDecorator; +import com.prolificinteractive.materialcalendarview.DayViewFacade; +import com.prolificinteractive.materialcalendarview.spans.DotSpan; + +import java.util.Collection; +import java.util.HashSet; + +public class EventDecorator implements DayViewDecorator { + + private final int color; + private final HashSet dates; + + public EventDecorator(int color, Collection dates) { + this.color = color; + this.dates = new HashSet<>(dates); + } + + @Override + public boolean shouldDecorate(CalendarDay day) { + return dates.contains(day); + } + + @Override + public void decorate(DayViewFacade view) { + view.addSpan(new DotSpan(8, color)); + } +} diff --git a/android/app/src/main/res/layout/fragment_appointment.xml b/android/app/src/main/res/layout/fragment_appointment.xml index a8da443e..10e17d48 100644 --- a/android/app/src/main/res/layout/fragment_appointment.xml +++ b/android/app/src/main/res/layout/fragment_appointment.xml @@ -29,15 +29,35 @@ android:contentDescription="Open menu"/> + + + +