update calendar

This commit is contained in:
Ilia Kurtov 2016-10-18 16:07:53 +03:00
parent d1d9055ba7
commit adcf29eeea
9 changed files with 920 additions and 0 deletions

View File

@ -28,6 +28,7 @@ dependencies {
compile 'io.reactivex:rxandroid:1.2.1'
provided 'com.android.support:appcompat-v7:24.2.1'
provided 'com.android.support:recyclerview-v7:24.2.1'
provided 'com.squareup.retrofit2:retrofit:2.1.0'
provided 'com.squareup.okhttp3:okhttp:3.4.1'

View File

@ -0,0 +1,309 @@
/*
* 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 monthName Name of month;
* @param firstMonth True if bind called for the first month in calendar.
*/
protected abstract void bindHeaderItem(@NonNull final THeaderViewHolder viewHolder, @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 String monthName = monthsNames != null ? monthsNames[((CalendarHeaderItem) calendarItem).getMonth()]
: String.valueOf(((CalendarHeaderItem) calendarItem).getMonth());
bindHeaderItem((THeaderViewHolder) holder, 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
}
}

View File

@ -0,0 +1,87 @@
/*
* 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;
/**
* Created by Ilia Kurtov on 17/03/2016.
* Calendar header item for showing headers for months in calendar.
*/
public class CalendarDayItem implements CalendarItem {
private final long dateOfFirstDay;
private final int positionOfFirstDate;
private final int startRange;
private final int endRange;
@NonNull
private final ComparingToToday comparingToToday;
public CalendarDayItem(final long dateOfFirstDay,
final int positionOfFirstDate,
final int startRange,
final int endRange,
@NonNull final ComparingToToday comparingToToday) {
this.dateOfFirstDay = dateOfFirstDay;
this.positionOfFirstDate = positionOfFirstDate;
this.startRange = startRange;
this.endRange = endRange;
this.comparingToToday = comparingToToday;
}
/**
* Returns date of the first date in millis in this calendar range.
*
* @return Date of first date in this item in millis.
*/
public long getDateOfFirstDay() {
return dateOfFirstDay;
}
/**
* Returns position of calendar cell for the first date.
*
* @return Position of calendar cell for the first date.
*/
public int getPositionOfFirstDay() {
return positionOfFirstDate;
}
@Override
public int getStartRange() {
return startRange;
}
@Override
public int getEndRange() {
return endRange;
}
/**
* Returns comparison of current item to today.
*
* @return comparison of current item to today.
*/
@NonNull
public ComparingToToday getComparingToToday() {
return comparingToToday;
}
}

View File

@ -0,0 +1,46 @@
/*
* 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;
/**
* Created by Ilia Kurtov on 17/03/2016.
* Calendar item for showing empty cells in calendar.
*/
public class CalendarEmptyItem implements CalendarItem {
private final int startRange;
private final int endRange;
public CalendarEmptyItem(final int startRange, final int endRange) {
this.startRange = startRange;
this.endRange = endRange;
}
@Override
public int getStartRange() {
return startRange;
}
@Override
public int getEndRange() {
return endRange;
}
}

View File

@ -0,0 +1,57 @@
/*
* 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;
/**
* Created by Ilia Kurtov on 17/03/2016.
* Calendar header item for showing headers for months in calendar.
*/
public class CalendarHeaderItem implements CalendarItem {
private final int month;
private final int startRange;
private final int endRange;
public CalendarHeaderItem(final int month, final int startRange, final int endRange) {
this.month = month;
this.startRange = startRange;
this.endRange = endRange;
}
/**
* Returns number of month (where 0 is January and 11 is December).
*
* @return Number of month (where 0 is January and 11 is December).
*/
public int getMonth() {
return month;
}
@Override
public int getStartRange() {
return startRange;
}
@Override
public int getEndRange() {
return endRange;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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;
/**
* Created by Ilia Kurtov on 17/03/2016.
* Interface for items for {@link CalendarAdapter}. Instead of storing data about all calendar cells separately,
* it sores list of this items. CalendarItem represents range with the same calendar items.
*/
public interface CalendarItem {
/**
* Returns number of starting cell of this range.
*
* @return number of starting cell of this range.
*/
int getStartRange();
/**
* Returns number of ending cell of this range.
*
* @return number of ending cell of this range.
*/
int getEndRange();
}

View File

@ -0,0 +1,73 @@
/*
* 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.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import ru.touchin.roboswag.core.log.Lc;
/**
* Created by Ilia Kurtov on 17/03/2016.
* Specific {@link RecyclerView} that works with {@link CalendarAdapter}. It optimizes speed of the calendar.
*/
public class CalendarRecyclerView extends RecyclerView {
private static final int HEADER_MAX_ELEMENTS_IN_A_ROW = 1;
private static final int EMPTY_MAX_ELEMENTS_IN_A_ROW = 6;
private static final int DAY_MAX_ELEMENTS_IN_A_ROW = 7;
public CalendarRecyclerView(@NonNull final Context context) {
this(context, null);
}
public CalendarRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
this(context, attrs, 0);
}
public CalendarRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.HEADER_ITEM_TYPE, HEADER_MAX_ELEMENTS_IN_A_ROW * 3);
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.EMPTY_ITEM_TYPE, EMPTY_MAX_ELEMENTS_IN_A_ROW * 3);
getRecycledViewPool().setMaxRecycledViews(CalendarAdapter.DAY_ITEM_TYPE, DAY_MAX_ELEMENTS_IN_A_ROW * 3);
setItemViewCacheSize(0);
}
/**
* Used to set adapter that extends from {@link CalendarAdapter}.
*
* @param calendarAdapter Adapter that extends from {@link CalendarAdapter}.
*/
// This suppress needed for using only specific CalendarAdapter}
@SuppressWarnings("PMD.UselessOverridingMethod")
public void setAdapter(@NonNull final CalendarAdapter calendarAdapter) {
super.setAdapter(calendarAdapter);
}
@Override
@Deprecated
public void setAdapter(final Adapter adapter) {
Lc.assertion("Unsupported adapter class. Use CalendarAdapter instead.");
}
}

View File

@ -0,0 +1,272 @@
/*
* 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 org.joda.time.DateTime;
import org.joda.time.DateTimeFieldType;
import org.joda.time.Days;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import ru.touchin.roboswag.core.log.Lc;
/**
* Created by Ilia Kurtov on 17/03/2016.
* Utility class to simplify working with {@link CalendarAdapter}.
*/
public final class CalendarUtils {
private static final int DAYS_IN_WEEK = 7;
private static final long ONE_DAY = TimeUnit.DAYS.toMillis(1);
/**
* Method finds CalendarItem for specified position. Find process is optimized and use binary search algorithm.
*
* @param calendarItems List of {@link CalendarItem} where need to find specific element;
* @param position Position of adapter;
* @return CalendarItem for specified position.
*/
@Nullable
public static CalendarItem findItemByPosition(@NonNull final List<CalendarItem> calendarItems, final long position) {
return find(calendarItems, position, false);
}
/**
* Method finds position of Header that respond to requested position.
*
* @param calendarItems List of {@link CalendarItem} where need to find specific element;
* @param position Position of adapter;
* @return Position of Header that respond to requested position.
* Returns null if Header or related CalendarItem was not found for specified position.
*/
@Nullable
public static Integer findPositionOfSelectedMonth(@NonNull final List<CalendarItem> calendarItems, final long position) {
final CalendarItem calendarItem = find(calendarItems, position, true);
if (calendarItem != null) {
return calendarItem.getStartRange();
}
return null;
}
/**
* Method finds position of calendar cell that respond to specified date.
*
* @param calendarItems List of {@link CalendarItem} where need to find specific element;
* @param date Requested date in milliseconds.
* @return Position of Calendar cell that that has specific date.
* Returns null if CalendarItem was not found for specified position.
*/
@Nullable
public static Integer findPositionByDate(@NonNull final List<CalendarItem> calendarItems, final long date) {
int low = 0;
int high = calendarItems.size() - 1;
int addition = 0;
float count = 0;
while (true) {
final int mid = (low + high) / 2 + addition;
if (calendarItems.get(mid) instanceof CalendarDayItem) {
if (date < ((CalendarDayItem) calendarItems.get(mid)).getDateOfFirstDay()) {
if (mid == 0) {
Lc.assertion("Selected date smaller then min date in calendar");
return null;
}
high = mid - 1;
} else {
final long endDate = ((CalendarDayItem) calendarItems.get(mid)).getDateOfFirstDay()
+ (calendarItems.get(mid).getEndRange() - calendarItems.get(mid).getStartRange()) * ONE_DAY;
if (date > endDate) {
if (mid == calendarItems.size()) {
Lc.assertion("Selected date bigger then max date in calendar");
return null;
}
low = mid + 1;
} else {
return (int) (calendarItems.get(mid).getStartRange()
+ (date - (((CalendarDayItem) calendarItems.get(mid)).getDateOfFirstDay())) / ONE_DAY);
}
}
count = 0;
addition = 0;
} else {
count++;
addition = ((int) Math.ceil(count / 2)) * ((int) (StrictMath.pow(-1, (count - 1))));
}
}
}
/**
* Create list of {@link CalendarItem} according to start and end Dates.
*
* @param startDate Start date of the range;
* @param endDate End date of the range;
* @return List of CalendarItems that could be one of these: {@link CalendarHeaderItem}, {@link CalendarDayItem} or {@link CalendarEmptyItem}.
*/
@NonNull
@SuppressWarnings("checkstyle:MethodLength")
public static List<CalendarItem> fillRanges(@NonNull final DateTime startDate, @NonNull final DateTime endDate) {
final DateTime cleanStartDate = startDate.withTimeAtStartOfDay();
final DateTime cleanEndDate = endDate.plusDays(1).withTimeAtStartOfDay();
DateTime tempTime = cleanStartDate;
final List<CalendarItem> calendarItems = fillCalendarTillCurrentDate(cleanStartDate, tempTime);
tempTime = tempTime.plusDays(Days.ONE.getDays());
final int totalDaysCount = Days.daysBetween(tempTime, cleanEndDate).getDays();
int shift = calendarItems.get(calendarItems.size() - 1).getEndRange();
int firstDate = tempTime.getDayOfMonth() - 1;
int daysEnded = 1;
while (true) {
final int daysInCurrentMonth = tempTime.dayOfMonth().getMaximumValue();
final long firstRangeDate = tempTime.getMillis();
if ((daysEnded + (daysInCurrentMonth - firstDate)) <= totalDaysCount) {
tempTime = tempTime.plusMonths(1).withDayOfMonth(1);
calendarItems.add(new CalendarDayItem(firstRangeDate, firstDate + 1, shift + daysEnded,
shift + daysEnded + (daysInCurrentMonth - firstDate) - 1, ComparingToToday.AFTER_TODAY));
daysEnded += daysInCurrentMonth - firstDate;
if (daysEnded == totalDaysCount) {
break;
}
firstDate = 0;
final int firstDayInWeek = tempTime.getDayOfWeek() - 1;
if (firstDayInWeek != 0) {
calendarItems.add(new CalendarEmptyItem(shift + daysEnded, shift + daysEnded + (DAYS_IN_WEEK - firstDayInWeek - 1)));
shift += (DAYS_IN_WEEK - firstDayInWeek);
}
calendarItems.add(new CalendarHeaderItem(tempTime.getMonthOfYear() - 1, shift + daysEnded, shift + daysEnded));
shift += 1;
if (firstDayInWeek != 0) {
calendarItems.add(new CalendarEmptyItem(shift + daysEnded, shift + daysEnded + firstDayInWeek - 1));
shift += firstDayInWeek;
}
} else {
calendarItems.add(new CalendarDayItem(firstRangeDate, firstDate + 1, shift + daysEnded, shift + totalDaysCount,
ComparingToToday.AFTER_TODAY));
break;
}
}
return calendarItems;
}
@Nullable
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity", "PMD.StdCyclomaticComplexity"})
private static CalendarItem find(@NonNull final List<CalendarItem> calendarItems, final long position, final boolean getHeaderPosition) {
int low = 0;
int high = calendarItems.size() - 1;
while (true) {
final int mid = (low + high) / 2;
if (position < calendarItems.get(mid).getStartRange()) {
if (mid == 0 || position > calendarItems.get(mid - 1).getEndRange()) {
Lc.assertion("CalendarAdapter cannot find item with that position");
return null;
}
high = mid - 1;
} else if (position > calendarItems.get(mid).getEndRange()) {
if (mid == calendarItems.size() || position < calendarItems.get(mid + 1).getStartRange()) {
Lc.assertion("CalendarAdapter cannot find item with that position");
return null;
}
low = mid + 1;
} else {
if (getHeaderPosition) {
int calendarShift = mid;
while (true) {
calendarShift--;
if (calendarShift == -1) {
return null;
}
if (calendarItems.get(calendarShift) instanceof CalendarHeaderItem) {
return calendarItems.get(calendarShift);
}
}
}
return calendarItems.get(mid);
}
}
}
@NonNull
private static List<CalendarItem> fillCalendarTillCurrentDate(@NonNull final DateTime cleanStartDate, @NonNull final DateTime startDate) {
DateTime temp = startDate;
final List<CalendarItem> calendarItems = new ArrayList<>();
int shift = 0;
final int firstDate = temp.getDayOfMonth() - 1; //?? - 1 ?
// add first month header
calendarItems.add(new CalendarHeaderItem(temp.get(DateTimeFieldType.monthOfYear()) - 1, shift, shift)); // is Month starts from 1 or 0 ?
temp = temp.withDayOfMonth(1);
shift += 1;
final int firstDayInTheWeek = temp.getDayOfWeek() - 1;
// check if first day is Monday. If not - add empty items. Otherwise do nothing
if (firstDayInTheWeek != 0) {
calendarItems.add(new CalendarEmptyItem(shift, shift + firstDayInTheWeek - 1));
}
shift += firstDayInTheWeek;
// add range with days before today
calendarItems.add(new CalendarDayItem(temp.getMillis(), 1, shift, shift + firstDate - 1, ComparingToToday.BEFORE_TODAY));
shift += firstDate;
// add today item
temp = cleanStartDate;
calendarItems.add(new CalendarDayItem(temp.getMillis(), firstDate + 1, shift, shift, ComparingToToday.TODAY));
//add empty items and header if current day the last day in the month
if (temp.getDayOfMonth() == temp.dayOfMonth().getMaximumValue()) {
addItemsIfCurrentDayTheLastDayInTheMonth(startDate, calendarItems);
}
return calendarItems;
}
private static void addItemsIfCurrentDayTheLastDayInTheMonth(@NonNull final DateTime dateTime,
@NonNull final List<CalendarItem> calendarItems) {
int shift = calendarItems.get(calendarItems.size() - 1).getEndRange();
final DateTime nextMonthFirstDay = dateTime.plusDays(1);
final int firstFayInNextMonth = nextMonthFirstDay.getDayOfWeek() - 1;
calendarItems.add(new CalendarEmptyItem(shift, shift + (7 - firstFayInNextMonth) - 1));
shift += 7 - firstFayInNextMonth;
calendarItems.add(new CalendarHeaderItem(nextMonthFirstDay.getMonthOfYear() + 1, shift, shift));
shift += 1;
calendarItems.add(new CalendarEmptyItem(shift, shift + firstFayInNextMonth - 1));
}
private CalendarUtils() {
}
}

View File

@ -0,0 +1,32 @@
/*
* 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;
/**
* Created by Ilia Kurtov on 18/03/2016.
* Show the comparison between a date and today.
*/
public enum ComparingToToday {
BEFORE_TODAY,
TODAY,
AFTER_TODAY
}