/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 com.philliphsu.bottomsheetpickers.date;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import com.philliphsu.bottomsheetpickers.Utils;
import com.philliphsu.bottomsheetpickers.date.MonthView.OnDayClickListener;
import java.util.ArrayList;
import java.util.HashMap;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
/**
* An adapter for a pager of {@link MonthView} items.
*/
class PagingMonthAdapter extends PagerAdapter implements OnDayClickListener {
private static final String TAG = "SimpleMonthAdapter";
private static final String KEY_POSITIONS = "positions";
private static final String KEY_MONTH_YEAR_TITLES = "month_year_titles";
private final Context mContext;
private final boolean mThemeDark;
private final int mAccentColor;
private final SparseArray<String> mMonthYearTitles = new SparseArray<>();
private final ArrayList<MonthView> mMonthViews = new ArrayList<>();
protected final DatePickerController mController;
private CalendarDay mSelectedDay;
private MonthView mCurrentPrimaryItem;
protected static int WEEK_7_OVERHANG_HEIGHT = 7;
protected static final int MONTHS_IN_YEAR = 12;
public PagingMonthAdapter(Context context,
DatePickerController controller) {
this(context, controller, false);
}
public PagingMonthAdapter(Context context,
DatePickerController controller,
boolean themeDark) {
this(context, controller, themeDark, Utils.getThemeAccentColor(context));
}
public PagingMonthAdapter(Context context, DatePickerController controller,
boolean themeDark, int accentColor) {
mContext = context;
mController = controller;
mThemeDark = themeDark;
mAccentColor = accentColor;
init();
setSelectedDay(mController.getSelectedDay());
}
/**
* Updates the selected day and related parameters.
*
* @param day The day to highlight
*/
public void setSelectedDay(CalendarDay day) {
mSelectedDay = day;
notifyDataSetChanged(); // This won't refresh our views!
// In the ListView-based day picker, notifyDataSetChanged() would have refreshed this
// adapter's contents, so instantiateItem() is called again on the entire data set.
// This would have called each MonthView's setMonthParams() with the correct value for the
// selectedDay (either -1 or the actual selected day if the selection is in that month)
// and invalidate(). However, PagerAdapter's implementation of notifyDataSetChanged()
// won't do all this for us, so we have to manually do it.
for (MonthView mv : mMonthViews) {
mv.setSelectedDay(mv == mCurrentPrimaryItem ? day.day : -1);
mv.invalidate();
}
}
public CalendarDay getSelectedDay() {
return mSelectedDay;
}
/**
* Set up the gesture detector and selected time
*/
protected void init() {
mSelectedDay = new CalendarDay(System.currentTimeMillis());
}
@Override
public int getCount() {
// Add 1 to get the total number of years in the year range.
// e.g. There are 11 years in the range [2016, 2026].
return (mController.getMaxYear() - mController.getMinYear() + 1) * MONTHS_IN_YEAR;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
MonthView v;
HashMap<String, Integer> drawingParams = null;
v = createMonthView(mContext, mThemeDark, mAccentColor);
// Set up the new view
v.setClickable(true);
v.setOnDayClickListener(this);
if (drawingParams == null) {
drawingParams = new HashMap<>();
}
drawingParams.clear();
final int month = getMonth(position);
final int year = getYear(position);
int selectedDay = -1;
if (isSelectedDayInMonth(year, month)) {
selectedDay = mSelectedDay.day;
}
// Invokes requestLayout() to ensure that the recycled view is set with the appropriate
// height/number of weeks before being displayed.
v.reuse();
drawingParams.put(MonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
drawingParams.put(MonthView.VIEW_PARAMS_YEAR, year);
drawingParams.put(MonthView.VIEW_PARAMS_MONTH, month);
drawingParams.put(MonthView.VIEW_PARAMS_WEEK_START, mController.getFirstDayOfWeek());
v.setMonthParams(drawingParams);
v.invalidate();
container.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
mMonthYearTitles.append(position, v.getMonthAndYearString());
mMonthViews.add(v);
return v;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
mMonthYearTitles.delete(position);
mMonthViews.remove(object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
mCurrentPrimaryItem = (MonthView) object;
}
@Override
public CharSequence getPageTitle(int position) {
return mMonthYearTitles.get(position);
}
@Override
public Parcelable saveState() {
Bundle state = null;
final int size = mMonthYearTitles.size();
if (size > 0) {
state = new Bundle();
String[] titles = new String[size];
int[] positions = new int[size];
for (int i = 0; i < size; i++) {
titles[i] = mMonthYearTitles.valueAt(i);
positions[i] = mMonthYearTitles.keyAt(i);
}
state.putStringArray(KEY_MONTH_YEAR_TITLES, titles);
state.putIntArray(KEY_POSITIONS, positions);
}
return state;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle)state;
bundle.setClassLoader(loader);
String[] titles = bundle.getStringArray(KEY_MONTH_YEAR_TITLES);
int[] positions = bundle.getIntArray(KEY_POSITIONS);
mMonthYearTitles.clear();
if (titles != null && positions != null) {
// Both arrays should be the same size, so use either length.
for (int i = 0; i < titles.length; i++) {
mMonthYearTitles.append(positions[i], titles[i]);
}
}
}
}
public MonthView createMonthView(Context context) {
return createMonthView(context, false);
}
public MonthView createMonthView(Context context, boolean themeDark) {
return createMonthView(context, themeDark, mAccentColor);
}
public MonthView createMonthView(Context context, boolean themeDark, int accentColor) {
final MonthView monthView = new SimpleMonthView(context);
monthView.setDatePickerController(mController);
monthView.setTheme(context, themeDark);
monthView.setTodayNumberColor(accentColor);
monthView.setSelectedCirclePaintColor(accentColor);
return monthView;
}
private boolean isSelectedDayInMonth(int year, int month) {
return mSelectedDay.year == year && mSelectedDay.month == month;
}
@Override
public void onDayClick(MonthView view, CalendarDay day) {
if (day != null) {
onDayTapped(day);
}
}
/**
* Maintains the same hour/min/sec but moves the day to the tapped day.
*
* @param day The day that was tapped
*/
protected void onDayTapped(CalendarDay day) {
mController.tryVibrate();
mController.onDayOfMonthSelected(day.year, day.month, day.day);
setSelectedDay(day);
}
/**
* Returns the month (0 - 11) displayed at this position.
*/
final int getMonth(int position) {
return position % MONTHS_IN_YEAR;
}
/**
* Returns the year displayed at this position.
*/
final int getYear(int position) {
return position / MONTHS_IN_YEAR + mController.getMinYear();
}
/**
* @return The page position at which the given day is located.
*/
final int getPosition(CalendarDay day) {
int positionOfYear = MONTHS_IN_YEAR * (day.year - mController.getMinYear());
return positionOfYear + day.month;
}
}