added calendar view to appointments
- NOTE: may have to change appointments abit after backend is updated
This commit is contained in:
@@ -82,6 +82,8 @@ dependencies {
|
|||||||
implementation("com.github.bumptech.glide:glide:4.16.0")
|
implementation("com.github.bumptech.glide:glide:4.16.0")
|
||||||
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
|
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
|
||||||
|
|
||||||
|
implementation("com.github.prolificinteractive:material-calendarview:2.0.1")
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.ext.junit)
|
androidTestImplementation(libs.ext.junit)
|
||||||
androidTestImplementation(libs.espresso.core)
|
androidTestImplementation(libs.espresso.core)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -29,10 +30,21 @@ import com.example.petstoremobile.dtos.PageResponse;
|
|||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.fragments.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.AppointmentDetailFragment;
|
import com.example.petstoremobile.fragments.listfragments.detailfragments.AppointmentDetailFragment;
|
||||||
|
import com.example.petstoremobile.utils.EventDecorator;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
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.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
@@ -50,6 +62,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
private ImageButton hamburger;
|
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
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -58,10 +75,13 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
|
|
||||||
api = RetrofitClient.getAppointmentApi(requireContext());
|
api = RetrofitClient.getAppointmentApi(requireContext());
|
||||||
hamburger = view.findViewById(R.id.btnHamburger);
|
hamburger = view.findViewById(R.id.btnHamburger);
|
||||||
|
calendarView = view.findViewById(R.id.calendarView);
|
||||||
|
btnToggleCalendarMode = view.findViewById(R.id.btnToggleCalendarMode);
|
||||||
|
|
||||||
setupRecyclerView(view);
|
setupRecyclerView(view);
|
||||||
setupSearch(view);
|
setupSearch(view);
|
||||||
setupSwipeRefresh(view);
|
setupSwipeRefresh(view);
|
||||||
|
setupCalendar();
|
||||||
loadAppointmentData();
|
loadAppointmentData();
|
||||||
loadPets();
|
loadPets();
|
||||||
loadServices();
|
loadServices();
|
||||||
@@ -76,9 +96,60 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
listFragment.openDrawer();
|
listFragment.openDrawer();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btnToggleCalendarMode.setOnClickListener(v -> toggleCalendarMode());
|
||||||
|
|
||||||
return view;
|
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<CalendarDay> 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) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchAppointment);
|
etSearch = view.findViewById(R.id.etSearchAppointment);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -99,16 +170,25 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
|
|
||||||
private void filterAppointments(String query) {
|
private void filterAppointments(String query) {
|
||||||
filteredList.clear();
|
filteredList.clear();
|
||||||
if (query.isEmpty()) {
|
String lowerQuery = query.toLowerCase();
|
||||||
filteredList.addAll(appointmentList);
|
|
||||||
} else {
|
String selectedDateString = null;
|
||||||
String lower = query.toLowerCase();
|
if (selectedCalendarDay != null) {
|
||||||
for (AppointmentDTO a : appointmentList) {
|
selectedDateString = String.format(Locale.getDefault(), "%04d-%02d-%02d",
|
||||||
if ((a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lower))
|
selectedCalendarDay.getYear(), selectedCalendarDay.getMonth(), selectedCalendarDay.getDay());
|
||||||
|| (a.getServiceType() != null && a.getServiceType().toLowerCase().contains(lower))
|
}
|
||||||
|| (a.getPetName() != null && a.getPetName().toLowerCase().contains(lower))) {
|
|
||||||
filteredList.add(a);
|
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();
|
adapter.notifyDataSetChanged();
|
||||||
@@ -141,17 +221,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
if (lf != null) lf.loadFragment(detailFragment);
|
if (lf != null) lf.loadFragment(detailFragment);
|
||||||
}
|
}
|
||||||
public void onAppointmentSaved(int position, AppointmentDTO appointment) {
|
public void onAppointmentSaved(int position, AppointmentDTO appointment) {
|
||||||
if (position == -1) {
|
loadAppointmentData();
|
||||||
appointmentList.add(appointment);
|
|
||||||
} else {
|
|
||||||
appointmentList.set(position, appointment);
|
|
||||||
}
|
|
||||||
filterAppointments(etSearch.getText().toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAppointmentDeleted(int position) {
|
public void onAppointmentDeleted(int position) {
|
||||||
appointmentList.remove(position);
|
loadAppointmentData();
|
||||||
filterAppointments(etSearch.getText().toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -162,7 +236,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
private void loadAppointmentData() {
|
private void loadAppointmentData() {
|
||||||
if (swipeRefreshLayout != null)
|
if (swipeRefreshLayout != null)
|
||||||
swipeRefreshLayout.setRefreshing(true);
|
swipeRefreshLayout.setRefreshing(true);
|
||||||
api.getAllAppointments(0, 100).enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
api.getAllAppointments(0, 500).enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<PageResponse<AppointmentDTO>> call,
|
public void onResponse(Call<PageResponse<AppointmentDTO>> call,
|
||||||
Response<PageResponse<AppointmentDTO>> response) {
|
Response<PageResponse<AppointmentDTO>> response) {
|
||||||
@@ -171,6 +245,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
appointmentList.clear();
|
appointmentList.clear();
|
||||||
appointmentList.addAll(response.body().getContent());
|
appointmentList.addAll(response.body().getContent());
|
||||||
|
updateCalendarDecorators();
|
||||||
filterAppointments(etSearch != null ? etSearch.getText().toString() : "");
|
filterAppointments(etSearch != null ? etSearch.getText().toString() : "");
|
||||||
} else {
|
} else {
|
||||||
Log.e("AppointmentFragment", "Error: " + response.message());
|
Log.e("AppointmentFragment", "Error: " + response.message());
|
||||||
|
|||||||
@@ -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<CalendarDay> dates;
|
||||||
|
|
||||||
|
public EventDecorator(int color, Collection<CalendarDay> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,15 +29,35 @@
|
|||||||
android:contentDescription="Open menu"/>
|
android:contentDescription="Open menu"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Appointments"
|
android:text="Appointments"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnToggleCalendarMode"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_today"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:contentDescription="Toggle Calendar Mode"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.prolificinteractive.materialcalendarview.MaterialCalendarView
|
||||||
|
android:id="@+id/calendarView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
app:mcv_showOtherDates="all"
|
||||||
|
app:mcv_selectionColor="@color/accent_blue"
|
||||||
|
app:mcv_calendarMode="week"
|
||||||
|
app:mcv_tileHeight="40dp" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etSearchAppointment"
|
android:id="@+id/etSearchAppointment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|||||||
# Android operating system, and which are packaged with your app's APK
|
# Android operating system, and which are packaged with your app's APK
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
|
android.enableJetifier=true
|
||||||
# Enables namespacing of each library's R class so that its R class includes only the
|
# Enables namespacing of each library's R class so that its R class includes only the
|
||||||
# resources declared in the library itself and none from the library's dependencies,
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
|
|||||||
Reference in New Issue
Block a user