MrZaiko/Polysmee

View on GitHub
app/src/main/java/io/github/polysmee/calendar/fragments/CalendarActivityMyAppointmentsFragment.java

Summary

Maintainability
A
45 mins
Test Coverage
B
87%
package io.github.polysmee.calendar.fragments;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import io.github.polysmee.R;
import io.github.polysmee.agora.Command;
import io.github.polysmee.appointments.AppointmentActivity;
import io.github.polysmee.calendar.CalendarAppointmentInfo;
import io.github.polysmee.calendar.DailyCalendar;
import io.github.polysmee.database.Appointment;
import io.github.polysmee.database.DatabaseAppointment;
import io.github.polysmee.database.User;
import io.github.polysmee.database.databaselisteners.valuelisteners.LongValueListener;
import io.github.polysmee.database.databaselisteners.valuelisteners.StringSetValueListener;
import io.github.polysmee.database.databaselisteners.valuelisteners.StringValueListener;
import io.github.polysmee.internet.connection.InternetConnection;
import io.github.polysmee.room.RoomActivity;
import io.github.polysmee.login.MainUser;

import static io.github.polysmee.calendar.fragments.CalendarActivityFragmentsHelpers.goToAppointmentDetails;
import static io.github.polysmee.calendar.fragments.CalendarActivityFragmentsHelpers.setDayText;
import static io.github.polysmee.calendar.fragments.CalendarActivityFragmentsHelpers.setTodayDateInDailyCalendar;

public class CalendarActivityMyAppointmentsFragment extends Fragment {

    private StringSetValueListener userAppointmentsListener;
    private ViewGroup rootView;

    private LinearLayout scrollLayout;
    private LayoutInflater inflater;

    private User user;


    private final Map<String, View> appointmentIdsToView = new HashMap<>();
    private final Set<String> appointmentSet = new HashSet<>();
    private final Map<String, CalendarAppointmentInfo> appointmentInfoMap = new HashMap<>();

    //Commands to remove listeners
    private final List<Command> commandsToRemoveListeners = new ArrayList<Command>();


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        rootView = (ViewGroup) inflater.inflate(R.layout.fragment_calendar_activity_my_appointments, container, false);
        scrollLayout = rootView.findViewById(R.id.calendarActivityMyAppointmentsScrollLayout);
        this.inflater = inflater;
        setTodayDateInDailyCalendar(false);
        setDayText(rootView, false);
        user = MainUser.getMainUser();
        userAppointmentsListener = null;

        rootView.findViewById(R.id.calendarActivityCreateAppointmentButton).setOnClickListener((v) -> createAppointment());
        rootView.findViewById(R.id.todayDateMyAppointmentsCalendarActivity).setOnClickListener((v) -> chooseDate());

        setListenerUserAppointments();
        return rootView;
    }

    /**
     * Behavior of the appointment date button; will pop a date picker dialog to let the user
     * choose the date they want, and will add a listener to the user's appointment to show the appointments
     * they have that given day.
     */
    protected void chooseDate() {
        long epochTimeChosenDay = DailyCalendar.getDayEpochTimeAtMidnight(false);
        Date chosenDay = new Date(epochTimeChosenDay);

        Calendar calendarChosenDay = Calendar.getInstance();
        calendarChosenDay.setTime(chosenDay);

        new DatePickerDialog(getContext(), (view, year, monthOfYear, dayOfMonth) -> {
            DailyCalendar.setDayEpochTimeAtMidnight(year, monthOfYear, dayOfMonth, false);
            setDayText(rootView, false);
            scrollLayout.removeAllViewsInLayout();
            setListenerUserAppointments();
        }, calendarChosenDay.get(Calendar.YEAR), calendarChosenDay.get(Calendar.MONTH), calendarChosenDay.get(Calendar.DATE)).show();

    }

    @Override
    public void onResume() {
        super.onResume();
        setListenerUserAppointments();
    }

    @Override
    public void onDestroy() {
        Object dummyArgument = null;
        for(Command command : commandsToRemoveListeners) {
            command.execute(dummyArgument,dummyArgument);
        }

        super.onDestroy();
    }

    /*
     * Message to announce to the user that they're offline and that their appointment
     * will be added after they're connected
     */
    private void messageCreatingAppointmentOffline(){
        android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(getContext());
        builder.setTitle(R.string.offline_warning);
        builder.setMessage(R.string.offline_appointment);

        //add ok button
        builder.setPositiveButton(R.string.offline_ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent intent = new Intent(rootView.getContext(), AppointmentActivity.class);
                startActivity(intent);
            }
        });
        builder.show();
    }
    /*
     * Behavior of the create appointment button, depending on if the user is connected or not
     */
    private void createAppointment() {
        if(!InternetConnection.isOn()) {
            messageCreatingAppointmentOffline();
        } else {
            Intent intent = new Intent(rootView.getContext(), AppointmentActivity.class);
            startActivity(intent);
        }

    }

    /**
     * Changes the calendar's layout to show the user's daily appointments at the time
     * this method is called.
     * @param infos all the user's appointments
     */
    protected void changeCurrentCalendarLayout(Set<CalendarAppointmentInfo> infos) {
        List<CalendarAppointmentInfo> todayAppointments = DailyCalendar.getAppointmentsForTheDay(infos, false,true);
        if (!todayAppointments.isEmpty()) {
            for (CalendarAppointmentInfo appointment : todayAppointments) {
                addAppointmentToCalendarLayout(appointment);
            }
        }
    }

    /**
     * Creates an appointment's textual description following a certain format
     * to show in the calendar in an entry
     *
     * @param appointment the appointment's whose description is created
     * @param calendarEntry the entry which will hold the appointment's information
     */
    protected void createAppointmentEntry(CalendarAppointmentInfo appointment, View calendarEntry) {
        ((TextView) calendarEntry.findViewById(R.id.calendarEntryAppointmentTitle)).setText(appointment.getTitle());

        Date startDate = new Date(appointment.getStartTime());
        Date endDate = new Date((appointment.getStartTime() + appointment.getDuration()));
        Date current = new Date(System.currentTimeMillis());

        if (current.before(startDate))
            calendarEntry.setOnClickListener(v -> goToAppointmentDetails(appointment.getId(), this, rootView));
        else
            calendarEntry.setOnClickListener((v) -> launchRoomActivityWhenClickingOnDescription(appointment.getId()));

        SimpleDateFormat formatter = new SimpleDateFormat("HH:mm", Locale.getDefault());
        String appointmentDate = formatter.format(startDate) + " - " + formatter.format(endDate);
        ((TextView) calendarEntry.findViewById(R.id.calendarEntryAppointmentDate)).setText(appointmentDate);

        ImageView status = calendarEntry.findViewById(R.id.calendarEntryStatus);
        CalendarActivityFragmentsHelpers.setStatusImage(status,current,startDate,endDate);
    }


    /**
     * Every time the user clicks on an appointment's description in his daily calendar, the corresponding
     * room activity is launched if the appointment is ongoing or has ended.
     *
     * @param appointmentId the appointment's id which will see its room launched
     *                      when clicking on its description.
     */
    protected void launchRoomActivityWhenClickingOnDescription(String appointmentId) {
        Intent roomActivityIntent = new Intent(rootView.getContext(), RoomActivity.class);
        roomActivityIntent.putExtra(RoomActivity.APPOINTMENT_KEY, appointmentId);
        startActivity(roomActivityIntent);
    }

    /**
     * Adds an appointment to the calendar layout, as a calendar entry
     *
     * @param appointment the appointment to add
     */
    @SuppressLint("InflateParams")
    protected void addAppointmentToCalendarLayout(CalendarAppointmentInfo appointment) {
        ConstraintLayout appointmentEntryLayout = (ConstraintLayout) inflater.inflate(R.layout.element_calendar_entry, null);
        createAppointmentEntry(appointment, appointmentEntryLayout);
        appointmentEntryLayout.setOnLongClickListener(l -> exportToCalendarDialog(appointment));
        CalendarActivityFragmentsHelpers.addEntryToScrollLayout(rootView,scrollLayout,appointmentIdsToView,appointment,appointmentEntryLayout);
    }

    private boolean exportToCalendarDialog(CalendarAppointmentInfo appointment) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage("Do you want to export this appointment to your calendar ?")
                .setPositiveButton("Export", (dialog, id) -> {
                    String description = "Course: " + appointment.getCourse();

                    Intent intent = new Intent(Intent.ACTION_INSERT)
                            .setData(CalendarContract.Events.CONTENT_URI)
                            .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, appointment.getStartTime())
                            .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, appointment.getStartTime() + appointment.getDuration())
                            .putExtra(CalendarContract.Events.TITLE, appointment.getTitle())
                            .putExtra(CalendarContract.Events.DESCRIPTION, description);
                    getContext().startActivity(intent);
                })
                .setNegativeButton("Cancel", (dialog, id) -> {
                    // User cancelled the dialog
                });

        builder.create().show();
        return true;
    }

    /**
     * Adds a listener to the user's appointments so that every time one is added/removed, the layout
     * is updated. It also takes care of determining what should happen to the calendar's layout
     * if an appointment's parameters changes.
     */

    protected void setListenerUserAppointments() {
        if (userAppointmentsListener != null) {
            user.removeAppointmentsListener(userAppointmentsListener);
        }

        userAppointmentsListener = currentDayUserAppointmentsListener();
        user.getAppointmentsAndThen(userAppointmentsListener);
        commandsToRemoveListeners.add((x,y) -> user.removeAppointmentsListener(userAppointmentsListener));
    }

    /**
     *
     * @return the listener that will be set in the method "setListenerUserAppointments"
     */
    protected StringSetValueListener currentDayUserAppointmentsListener() {

        return setOfIds -> {
            Set<String> deletedAppointments = new HashSet<>(appointmentSet);
            Set<String> newAppointments = new HashSet<>(setOfIds);

            deletedAppointments.removeAll(newAppointments); //keep the deleted appointments
            newAppointments.removeAll(appointmentSet); //keep the new appointmnets

            for (String oldAppointmentId : deletedAppointments) { //delete all old appointments
                appointmentSet.remove(oldAppointmentId);
                appointmentInfoMap.remove(oldAppointmentId);
                if (appointmentIdsToView.containsKey(oldAppointmentId)) {
                    scrollLayout.removeView(appointmentIdsToView.get(oldAppointmentId));
                    scrollLayout.removeView(appointmentIdsToView.get(oldAppointmentId + 1));
                }
            }


                appointmentSet.addAll(newAppointments); //add all new appointments
                if (newAppointments.isEmpty()) { //if empty, update the layout and return
                    scrollLayout.removeAllViewsInLayout();
                    changeCurrentCalendarLayout(new HashSet<>(appointmentInfoMap.values()));
                    return;
                }
                for (String id : newAppointments) { //iterate only on the new appointments, to set their listener only once
                    Appointment appointment = new DatabaseAppointment(id);
                    CalendarAppointmentInfo appointmentInfo = new CalendarAppointmentInfo("", "", 0, 0, id,0);

                    LongValueListener startListener = (start) -> {
                        appointmentInfo.setStartTime(start);
                        LongValueListener durationListener = (duration) -> {
                            appointmentInfo.setDuration(duration);
                            StringValueListener titleListener = (title) -> {
                                appointmentInfo.setTitle((title));
                                if (!appointmentSet.contains(appointmentInfo.getId())) { //the appointment was removed; we thus have to remove it from the displayed appointments
                                    appointmentInfoMap.remove(appointmentInfo.getId());
                                    if (appointmentIdsToView.containsKey(id)) {
                                        scrollLayout.removeView(appointmentIdsToView.get(id));
                                        scrollLayout.removeView(appointmentIdsToView.get(id + 1));
                                        appointmentIdsToView.remove(id);
                                        appointmentIdsToView.remove(id + 1);
                                    }
                                } else {
                                    appointmentInfoMap.put(appointment.getId(), appointmentInfo);
                                    if (appointmentIdsToView.containsKey(appointmentInfo.getId())) { //the view is already there, we just need to update it.
                                        createAppointmentEntry(appointmentInfo, appointmentIdsToView.get(appointmentInfo.getId()));
                                    } else { //we add the new appointment and update the layout.
                                        scrollLayout.removeAllViewsInLayout();
                                        changeCurrentCalendarLayout(new HashSet<>(appointmentInfoMap.values()));
                                    }
                                }
                            };
                            appointment.getTitleAndThen(titleListener);
                            commandsToRemoveListeners.add((x,y) -> appointment.removeTitleListener(titleListener));
                        };
                        appointment.getDurationAndThen(durationListener);
                        commandsToRemoveListeners.add((x,y) -> appointment.removeDurationListener(durationListener));
                    };

                    appointment.getStartTimeAndThen(startListener);
                    commandsToRemoveListeners.add((x,y) -> appointment.removeStartListener(startListener));
                }
            };
        }



}