njomeau/zuluzulu

View on GitHub
app/src/main/java/ch/epfl/sweng/zuluzulu/fragments/EventFragment.java

Summary

Maintainability
B
4 hrs
Test Coverage
package ch.epfl.sweng.zuluzulu.fragments;

import android.app.DatePickerDialog;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.constraint.ConstraintLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Objects;

import ch.epfl.sweng.zuluzulu.adapters.EventArrayAdapter;
import ch.epfl.sweng.zuluzulu.CommunicationTag;
import ch.epfl.sweng.zuluzulu.firebase.DatabaseFactory;
import ch.epfl.sweng.zuluzulu.OnFragmentInteractionListener;
import ch.epfl.sweng.zuluzulu.R;
import ch.epfl.sweng.zuluzulu.fragments.superFragments.FragmentWithUser;
import ch.epfl.sweng.zuluzulu.structure.Event;
import ch.epfl.sweng.zuluzulu.structure.user.AuthenticatedUser;
import ch.epfl.sweng.zuluzulu.structure.user.User;
import ch.epfl.sweng.zuluzulu.structure.user.UserRole;

import static ch.epfl.sweng.zuluzulu.CommunicationTag.OPEN_CREATE_EVENT;

/**
 * A simple {@link Fragment} subclass.
 * Activities that contain this fragment must implement the
 * {@link OnFragmentInteractionListener} interface
 * to handle interaction events.
 * Use the {@link EventFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class EventFragment extends FragmentWithUser<User> {
    private final Date MIN_DATE = new Date(946724400000L);  // 01/01/2000
    private final Date MAX_DATE = new Date(1735729200000L); // 01/01/2025

    private ArrayList<Event> allEvents;
    private ArrayList<Event> followedEvents;
    private ArrayList<Event> eventsToFilter;
    private ArrayList<Event> eventsFiltered;

    private EventArrayAdapter event_adapter;

    private ListView listview_event;
    private Button button_event_all;
    private Button button_event_fav;

    private CheckBox checkbox_event_sort_name;
    private CheckBox checkbox_event_sort_date;
    private CheckBox checkbox_event_sort_like;

    private Comparator<Event> currentComparator;

    private EditText event_search_bar;

    private Calendar eventCalendar;
    private EditText event_fragment_from_date;
    private EditText event_fragment_to_date;
    private Date dateFrom;
    private Date dateTo;

    private ImageButton filter_button;
    private ConstraintLayout event_filter_constraint_layout;

    public EventFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param user user.
     * @return A new instance of fragment EventFragment.
     */
    public static EventFragment newInstance(User user) {
        if(user == null)
            throw new IllegalArgumentException("user can't be null");
        EventFragment fragment = new EventFragment();
        Bundle args = new Bundle();
        args.putSerializable(ARG_USER, user);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            user = (User) getArguments().getSerializable(ARG_USER);
            mListener.onFragmentInteraction(CommunicationTag.SET_TITLE, getResources().getString(R.string.drawer_events));
        }

        allEvents = new ArrayList<>();
        followedEvents = new ArrayList<>();
        eventsToFilter = allEvents;
        eventsFiltered = new ArrayList<>();
        event_adapter = new EventArrayAdapter(getContext(), eventsFiltered, mListener, user);
        currentComparator = Event.dateComparator();
        eventCalendar = Calendar.getInstance();
        fillEventLists();
    }

    // TODO: is it possible to reduce this ?
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_event, container, false);
        listview_event = view.findViewById(R.id.event_fragment_listview);
        listview_event.setAdapter(event_adapter);

        // Add event button displayed only if user is an Admin
        if (user.hasRole(UserRole.ADMIN)) {
            ImageButton button_event_add = view.findViewById(R.id.event_add_button);
            button_event_add.setVisibility(View.VISIBLE);
            button_event_add.setOnClickListener(v -> mListener.onFragmentInteraction(OPEN_CREATE_EVENT, null));
        }

        // Main buttons to switch between the all events or only followed events
        button_event_fav = view.findViewById(R.id.event_fragment_fav_button);
        button_event_all = view.findViewById(R.id.event_fragment_all_button);
        button_event_fav.setOnClickListener(v -> {
            if (user.isConnected())
                updateListView(button_event_fav, followedEvents);
            else
                Snackbar.make(Objects.requireNonNull(getView()), "Login to access your favorite event", 5000).show();
        });
        button_event_all.setOnClickListener(v -> updateListView(button_event_all, allEvents));

        checkbox_event_sort_name = view.findViewById(R.id.event_fragment_checkBox_sort_name);
        checkbox_event_sort_date = view.findViewById(R.id.event_fragment_checkBox_sort_date);
        checkbox_event_sort_like = view.findViewById(R.id.event_fragment_checkbox_sort_like);
        selectClickedCheckbox(checkbox_event_sort_date);

        event_fragment_from_date = view.findViewById(R.id.event_fragment_from_date);
        event_fragment_from_date.setOnClickListener(dateOnClick(true));
        event_fragment_to_date = view.findViewById(R.id.event_fragment_to_date);
        event_fragment_to_date.setOnClickListener(dateOnClick(false));


        event_search_bar = view.findViewById(R.id.event_fragment_search_bar);

        filter_button = view.findViewById(R.id.event_fragment_filter_button);
        event_filter_constraint_layout = view.findViewById(R.id.even_filter_constraintLayout);

        // All method the set the behaviour of the checkboxes, date selecting and name/description matching
        setFilteringWithText();
        setToggleFilterVisibilityBehaviour();
        setSortingBehaviourOnCheckbox(checkbox_event_sort_date, Event.dateComparator());
        setSortingBehaviourOnCheckbox(checkbox_event_sort_like, Event.likeComparator());
        setSortingBehaviourOnCheckbox(checkbox_event_sort_name, Event.nameComparator());

        return view;
    }

    /**
     * Fill the event list with the data from the Proxy
     * Before doing so, clear all event lists of the class to avoid duplication
     */
    private void fillEventLists() {
        DatabaseFactory.getDependency().getAllEvents(result -> {
            allEvents.clear();
            followedEvents.clear();
            eventsFiltered.clear();
            allEvents.addAll(result);
            eventsToFilter = allEvents;
            eventsFiltered.addAll(eventsToFilter);
            sortWithCurrentComparator();
        });
    }

    /**
     * Set the sorting behaviour of a Checkbox for the displayed list of event
     *
     * @param checkBox   checkbox that will be clicked
     * @param comparator comparator to use when the checkbox is clicked
     */
    private void setSortingBehaviourOnCheckbox(CheckBox checkBox, Comparator<Event> comparator) {
        checkBox.setOnClickListener(v -> {
            if (checkBox.isEnabled()) {
                selectClickedCheckbox(checkBox);
                currentComparator = comparator;
                sortWithCurrentComparator();
            }
        });
    }

    /**
     * Sort the displayed list of event using the currently selected comparator
     */
    private void sortWithCurrentComparator() {
        Collections.sort(eventsFiltered, currentComparator);
        event_adapter.notifyDataSetChanged();
    }

    /**
     * Set the behaviour of the search bar to filter events when a string is inputted
     */
    private void setFilteringWithText() {
        event_search_bar.addTextChangedListener(new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                filterWithText(s.toString());
                sortWithCurrentComparator();
            }

            @Override
            public void afterTextChanged(Editable s) {
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
        });
    }

    /**
     * Deselect all checkboxes and then select+disable the currently used checkbox
     *
     * @param checkBox checkbox to select and disable
     */
    private void selectClickedCheckbox(CheckBox checkBox) {
        checkbox_event_sort_date.setEnabled(true);
        checkbox_event_sort_like.setEnabled(true);
        checkbox_event_sort_name.setEnabled(true);
        checkbox_event_sort_date.setChecked(false);
        checkbox_event_sort_like.setChecked(false);
        checkbox_event_sort_name.setChecked(false);

        checkBox.setChecked(true);
        checkBox.setEnabled(false);
    }

    /**
     * @param selectedButton    the new selected button
     * @param newEventsToFilter the new events to filter
     */
    private void updateListView(Button selectedButton, ArrayList<Event> newEventsToFilter) {
        event_search_bar.getText().clear();
        event_search_bar.clearFocus();
        event_fragment_from_date.getText().clear();
        event_fragment_to_date.getText().clear();
        dateTo = null;
        dateFrom = null;

        followedEvents.clear();
        for (Event event : allEvents)
            if (((AuthenticatedUser) user).isFollowedEvent(event.getId()))
                followedEvents.add(event);

        button_event_all.setBackgroundColor(getResources().getColor(R.color.colorGrayDarkTransparent));
        button_event_fav.setBackgroundColor(getResources().getColor(R.color.colorGrayDarkTransparent));
        selectedButton.setBackgroundColor(getResources().getColor(R.color.colorTransparent));
        eventsToFilter = newEventsToFilter;
        eventsFiltered.clear();
        eventsFiltered.addAll(eventsToFilter);
        sortWithCurrentComparator();
    }

    /**
     * Set the toggle of filter options
     * When clicked, display all filtering options. Clicking again hide the interface.
     */
    public void setToggleFilterVisibilityBehaviour() {
        filter_button.setOnClickListener(v -> {
            if (event_filter_constraint_layout.getVisibility() == View.VISIBLE)
                event_filter_constraint_layout.setVisibility(View.GONE);
            else
                event_filter_constraint_layout.setVisibility(View.VISIBLE);
        });
    }

    /**
     * Filter the eventsToFilter using a string
     *
     * @param s substring that must be contained in the name/description of the event
     */
    private void filterWithText(String s) {
        dateFrom = null;
        dateTo = null;
        event_fragment_from_date.clearFocus();
        event_fragment_from_date.getText().clear();
        event_fragment_to_date.clearFocus();
        event_fragment_to_date.getText().clear();
        String keyWord = s.toLowerCase();
        eventsFiltered.clear();
        for (Event event : eventsToFilter) {
            if (event.getName().toLowerCase().contains(keyWord) ||
                    event.getShortDescription().toLowerCase().contains(keyWord) ||
                    event.getLongDescription().toLowerCase().contains((keyWord)))
                eventsFiltered.add(event);
        }
    }

    /**
     * Return an OnClickListener for a button that opens a DatePickedDialog when clicked
     *
     * @param startDate if it is the lower or upper bound date to be selected
     * @return the OnClickListener that prompt a DatePicker on click
     */
    private View.OnClickListener dateOnClick(boolean startDate) {
        return v -> {
            DatePickerDialog.OnDateSetListener datePicker = (view, year, monthOfYear, dayOfMonth) -> {
                eventCalendar.set(Calendar.YEAR, year);
                eventCalendar.set(Calendar.MONTH, monthOfYear);
                eventCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
                if (startDate)
                    dateFrom = eventCalendar.getTime();
                else
                    dateTo = eventCalendar.getTime();
                filterWithDate();
            };

            if (getContext() != null)
                new DatePickerDialog(getContext(), datePicker, eventCalendar
                        .get(Calendar.YEAR), eventCalendar.get(Calendar.MONTH),
                        eventCalendar.get(Calendar.DAY_OF_MONTH)).show();
        };
    }

    /**
     * Fill the eventsToFilter using dates
     */
    private void filterWithDate() {
        sanitizeDates();
        event_search_bar.getText().clear();
        eventsFiltered.clear();

        for (Event event : eventsToFilter) {
            if (event.getStartDate().after(dateFrom) && event.getStartDate().before(dateTo))
                eventsFiltered.add(event);
        }
        event_adapter.notifyDataSetChanged();
    }

    /**
     * Sanitize dates if they are null or before/after the minimal/maximal date
     * Also switch the bound dates if the are in the wrong order
     */
    private void sanitizeDates() {
        if (dateTo == null || dateTo.after(MAX_DATE))
            dateTo = MAX_DATE;
        if (dateFrom == null || dateFrom.before(MIN_DATE))
            dateFrom = MIN_DATE;

        // if dateTo is before dateFrom
        if (dateTo.before(dateFrom)) {
            Date tempDate = dateFrom;
            dateFrom = dateTo;
            dateTo = tempDate;
        }

        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy");
        event_fragment_from_date.setText(sdf.format(dateFrom));
        event_fragment_to_date.setText(sdf.format(dateTo));
    }
}