ru.touchin.templates.calendar.CalendarAdapter.java Source code

Java tutorial

Introduction

Here is the source code for ru.touchin.templates.calendar.CalendarAdapter.java

Source

/*
 *  Copyright (c) 2016 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
 *
 *  This file is part of RoboSwag library.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package ru.touchin.templates.calendar;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.ViewGroup;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;

import java.util.List;
import java.util.concurrent.TimeUnit;

import ru.touchin.roboswag.core.utils.ShouldNotHappenException;

/**
 * Created by Ilia Kurtov on 17/03/2016.
 * Adapter for Calendar view. Use with {@link CalendarRecyclerView}.
 *
 * @param <TDayViewHolder>    Type of ViewHolders of a day with a date;
 * @param <THeaderViewHolder> Type of ViewHolders of a months header;
 * @param <TEmptyViewHolder>  Type of ViewHolders of an empty cell.
 */
public abstract class CalendarAdapter<TDayViewHolder extends RecyclerView.ViewHolder, THeaderViewHolder extends RecyclerView.ViewHolder, TEmptyViewHolder extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    public static final int HEADER_ITEM_TYPE = 0;
    public static final int EMPTY_ITEM_TYPE = 1;
    public static final int DAY_ITEM_TYPE = 2;

    public static final int MONTHS_IN_YEAR = 12;
    public static final long ONE_DAY_LENGTH = TimeUnit.DAYS.toMillis(1);

    @NonNull
    private final List<CalendarItem> calendarItems;
    @Nullable
    private Integer startSelectionPosition;
    @Nullable
    private Integer endSelectionPosition;
    @Nullable
    private String[] monthsNames;

    /**
     * Constructor that takes all necessary data to initialize.
     *
     * @param startDate   First date in the calendar range;
     * @param endDate     Last date (not inclusive) in the calendar range;
     * @param monthsNames String array of months names where #0 is January and #11 is December.
     */
    public CalendarAdapter(@NonNull final DateTime startDate, @NonNull final DateTime endDate,
            @Nullable final String... monthsNames) {
        super();
        if (monthsNames != null && monthsNames.length == MONTHS_IN_YEAR) {
            this.monthsNames = monthsNames;
        }
        calendarItems = CalendarUtils.fillRanges(startDate, endDate);
        if (calendarItems.isEmpty()) {
            throw new ShouldNotHappenException(
                    "There is no items in calendar with startDate: " + DateTimeFormat.fullDate().print(startDate)
                            + ", and endDate: " + DateTimeFormat.fullDate().print(endDate));
        }
    }

    /**
     * Set selected dates range in calendar. Call this method before attaching this adapter to {@link CalendarRecyclerView}.
     *
     * @param startSelectionDate First date that should be selected;
     * @param endSelectionDate   Last date that should be selected (inclusive).
     */
    public void setSelectedRange(@Nullable final DateTime startSelectionDate,
            @Nullable final DateTime endSelectionDate) {
        if (startSelectionDate != null) {
            startSelectionPosition = CalendarUtils.findPositionByDate(calendarItems,
                    startSelectionDate.withTimeAtStartOfDay().getMillis());
        }
        if (endSelectionDate != null) {
            endSelectionPosition = CalendarUtils.findPositionByDate(calendarItems,
                    endSelectionDate.withTimeAtStartOfDay().getMillis());
        }

        notifySelectedDaysChanged();
    }

    /**
     * Method finds the number of the first cell of selected range.
     *
     * @param departure Pass true if {@link CalendarRecyclerView} connected with this adapter should select departure (pass true) date
     *                  or arrival (pass false).
     * @return position of the cell to scroll to at the calendar view opening.
     */
    @Nullable
    public Integer getPositionToScroll(final boolean departure) {
        if (departure && startSelectionPosition != null) {
            return CalendarUtils.findPositionOfSelectedMonth(calendarItems, startSelectionPosition);
        }
        if (!departure && endSelectionPosition != null) {
            return CalendarUtils.findPositionOfSelectedMonth(calendarItems, endSelectionPosition);
        }
        if (!departure && startSelectionPosition != null) {
            return CalendarUtils.findPositionOfSelectedMonth(calendarItems, startSelectionPosition);
        }
        return null;
    }

    private void notifySelectedDaysChanged() {
        if (startSelectionPosition == null && endSelectionPosition == null) {
            return;
        }
        if (startSelectionPosition == null) {
            notifyItemRangeChanged(endSelectionPosition, 1);
            return;
        }
        if (endSelectionPosition == null) {
            notifyItemRangeChanged(startSelectionPosition, 1);
            return;
        }
        notifyItemRangeChanged(startSelectionPosition, endSelectionPosition - startSelectionPosition);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
        switch (viewType) {
        case HEADER_ITEM_TYPE:
            return createHeaderViewHolder(parent);
        case EMPTY_ITEM_TYPE:
            return createEmptyViewHolder(parent);
        case DAY_ITEM_TYPE:
            return createDayViewHolder(parent);
        default:
            return null;
        }
    }

    /**
     * Method that creates Header ViewHolder with type of THeaderViewHolder.
     *
     * @param parent {@link ViewGroup} for inflating ViewHolder;
     * @return New THeaderViewHolder;
     */
    protected abstract THeaderViewHolder createHeaderViewHolder(final ViewGroup parent);

    /**
     * Method that creates Empty ViewHolder with type of TEmptyViewHolder.
     *
     * @param parent {@link ViewGroup} for inflating ViewHolder;
     * @return New TEmptyViewHolder;
     */
    protected abstract TEmptyViewHolder createEmptyViewHolder(final ViewGroup parent);

    /**
     * Method that creates Day ViewHolder with type of TDayViewHolder.
     *
     * @param parent {@link ViewGroup} for inflating ViewHolder;
     * @return New TDayViewHolder;
     */
    protected abstract TDayViewHolder createDayViewHolder(final ViewGroup parent);

    /**
     * Bind data to a Header ViewHolder.
     *
     * @param viewHolder ViewHolder for binding;
     * @param year       year;
     * @param monthName  Name of month;
     * @param firstMonth True if bind called for the first month in calendar.
     */
    protected abstract void bindHeaderItem(@NonNull final THeaderViewHolder viewHolder, final int year,
            @NonNull final String monthName, final boolean firstMonth);

    /**
     * Bind data to an Empty ViewHolder.
     *
     * @param viewHolder    ViewHolder for binding;
     * @param selectionMode Either {@link SelectionMode#SELECTED_MIDDLE} or {@link SelectionMode#NOT_SELECTED} can be here.
     */
    protected abstract void bindEmptyItem(@NonNull final TEmptyViewHolder viewHolder,
            @NonNull final SelectionMode selectionMode);

    /**
     * Bind data to a Day ViewHolder.
     *
     * @param viewHolder    ViewHolder for binding;
     * @param day           Text with number of a day. Eg "1" or "29";
     * @param date          Date of current day;
     * @param selectionMode Selection mode for this item;
     * @param dateState     Shows calendar date state for this item.
     */
    protected abstract void bindDayItem(@NonNull final TDayViewHolder viewHolder, @NonNull final String day,
            @NonNull final DateTime date, @NonNull final SelectionMode selectionMode,
            @NonNull final ComparingToToday dateState);

    @Override
    @SuppressWarnings("unchecked")
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        final CalendarItem calendarItem = CalendarUtils.findItemByPosition(calendarItems, position);

        if (calendarItem instanceof CalendarHeaderItem) {
            final StaggeredGridLayoutManager.LayoutParams layoutParams = new StaggeredGridLayoutManager.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            layoutParams.setFullSpan(true);
            holder.itemView.setLayoutParams(layoutParams);
            final CalendarHeaderItem calendarHeaderItem = (CalendarHeaderItem) calendarItem;
            final String monthName = monthsNames != null ? monthsNames[calendarHeaderItem.getMonth()]
                    : String.valueOf(calendarHeaderItem.getMonth());
            bindHeaderItem((THeaderViewHolder) holder, calendarHeaderItem.getYear(), monthName, position == 0);
        } else if (calendarItem instanceof CalendarEmptyItem) {
            if (startSelectionPosition != null && endSelectionPosition != null && position >= startSelectionPosition
                    && position <= endSelectionPosition) {
                bindEmptyItem((TEmptyViewHolder) holder, SelectionMode.SELECTED_MIDDLE);
            } else {
                bindEmptyItem((TEmptyViewHolder) holder, SelectionMode.NOT_SELECTED);
            }
        } else if (calendarItem instanceof CalendarDayItem) {
            bindDay((TDayViewHolder) holder, position, calendarItem);
        }
    }

    //TODO fix suppress
    @SuppressWarnings("PMD.CyclomaticComplexity")
    private void bindDay(final TDayViewHolder holder, final int position, final CalendarItem calendarItem) {
        final String currentDay = String.valueOf(
                ((CalendarDayItem) calendarItem).getPositionOfFirstDay() + position - calendarItem.getStartRange());
        final DateTime currentDate = new DateTime(((CalendarDayItem) calendarItem).getDateOfFirstDay()
                + (position - calendarItem.getStartRange()) * ONE_DAY_LENGTH);
        final ComparingToToday dateState = ((CalendarDayItem) calendarItem).getComparingToToday();
        if (startSelectionPosition != null && position == startSelectionPosition) {
            if (endSelectionPosition == null || endSelectionPosition.equals(startSelectionPosition)) {
                bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_ONE_ONLY, dateState);
                return;
            }
            bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_FIRST, dateState);
            return;
        }
        if (endSelectionPosition != null && position == endSelectionPosition) {
            bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_LAST, dateState);
            return;
        }
        if (startSelectionPosition != null && endSelectionPosition != null && position >= startSelectionPosition
                && position <= endSelectionPosition) {
            bindDayItem(holder, currentDay, currentDate, SelectionMode.SELECTED_MIDDLE, dateState);
            return;
        }

        bindDayItem(holder, currentDay, currentDate, SelectionMode.NOT_SELECTED, dateState);
    }

    @Override
    public int getItemViewType(final int position) {
        final CalendarItem calendarItem = CalendarUtils.findItemByPosition(calendarItems, position);

        if (calendarItem instanceof CalendarHeaderItem) {
            return HEADER_ITEM_TYPE;
        } else if (calendarItem instanceof CalendarEmptyItem) {
            return EMPTY_ITEM_TYPE;
        } else if (calendarItem instanceof CalendarDayItem) {
            return DAY_ITEM_TYPE;
        }

        return super.getItemViewType(position);
    }

    @Override
    public int getItemCount() {
        return calendarItems.isEmpty() ? 0 : calendarItems.get(calendarItems.size() - 1).getEndRange();
    }

    /**
     * Selection mode that shows the type of selection of a calendar cell.
     */
    public enum SelectionMode {

        /**
         * Selection mode for the case when first date in the calendar range selected
         * (not first and last simultaneously; for this purpose see {@link #SELECTED_ONE_ONLY}).
         */
        SELECTED_FIRST,
        /**
         * Selection mode for the case when date in a middle of the calendar range selected.
         */
        SELECTED_MIDDLE,
        /**
         * Selection mode for the case when last date in the calendar range selected
         * (not last and first simultaneously; for this purpose see {@link #SELECTED_ONE_ONLY}).
         */
        SELECTED_LAST,
        /**
         * Selection mode for the case when only one date selected.
         */
        SELECTED_ONE_ONLY,
        /**
         * Selection mode for the case when nothing selected.
         */
        NOT_SELECTED

    }

}