Attachments to chat #133

Merged
RecentRunner merged 3 commits from AttachmentsToChat into main 2026-04-04 16:10:39 -06:00
5 changed files with 149 additions and 20 deletions
Showing only changes of commit 5fa9cfd5d6 - Show all commits

View File

@@ -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)

View File

@@ -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<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) {
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<PageResponse<AppointmentDTO>>() {
api.getAllAppointments(0, 500).enqueue(new Callback<PageResponse<AppointmentDTO>>() {
@Override
public void onResponse(Call<PageResponse<AppointmentDTO>> call,
Response<PageResponse<AppointmentDTO>> 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());

View File

@@ -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));
}
}

View File

@@ -29,15 +29,35 @@
android:contentDescription="Open menu"/>
<TextView
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Appointments"
android:textColor="@color/white"
android:textSize="20sp"
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>
<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
android:id="@+id/etSearchAppointment"
android:layout_width="match_parent"

View File

@@ -15,6 +15,8 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
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
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library