1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.annotation.IdRes;
20 import android.annotation.LayoutRes;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.res.ColorStateList;
25 import android.content.res.TypedArray;
26 import android.graphics.Rect;
27 import android.icu.util.Calendar;
28 import android.util.SparseArray;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.SimpleMonthView.OnDayClickListener;
33 
34 import com.android.internal.widget.PagerAdapter;
35 
36 /**
37  * An adapter for a list of {@link android.widget.SimpleMonthView} items.
38  */
39 class DayPickerPagerAdapter extends PagerAdapter {
40     private static final int MONTHS_IN_YEAR = 12;
41 
42     private final Calendar mMinDate = Calendar.getInstance();
43     private final Calendar mMaxDate = Calendar.getInstance();
44 
45     private final SparseArray<ViewHolder> mItems = new SparseArray<>();
46 
47     private final LayoutInflater mInflater;
48     private final int mLayoutResId;
49     private final int mCalendarViewId;
50 
51     private Calendar mSelectedDay = null;
52 
53     private int mMonthTextAppearance;
54     private int mDayOfWeekTextAppearance;
55     private int mDayTextAppearance;
56 
57     private ColorStateList mCalendarTextColor;
58     private ColorStateList mDaySelectorColor;
59     private ColorStateList mDayHighlightColor;
60 
61     private OnDaySelectedListener mOnDaySelectedListener;
62 
63     private int mCount;
64     private int mFirstDayOfWeek;
65 
DayPickerPagerAdapter(@onNull Context context, @LayoutRes int layoutResId, @IdRes int calendarViewId)66     public DayPickerPagerAdapter(@NonNull Context context, @LayoutRes int layoutResId,
67             @IdRes int calendarViewId) {
68         mInflater = LayoutInflater.from(context);
69         mLayoutResId = layoutResId;
70         mCalendarViewId = calendarViewId;
71 
72         final TypedArray ta = context.obtainStyledAttributes(new int[] {
73                 com.android.internal.R.attr.colorControlHighlight});
74         mDayHighlightColor = ta.getColorStateList(0);
75         ta.recycle();
76     }
77 
setRange(@onNull Calendar min, @NonNull Calendar max)78     public void setRange(@NonNull Calendar min, @NonNull Calendar max) {
79         mMinDate.setTimeInMillis(min.getTimeInMillis());
80         mMaxDate.setTimeInMillis(max.getTimeInMillis());
81 
82         final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
83         final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
84         mCount = diffMonth + MONTHS_IN_YEAR * diffYear + 1;
85 
86         // Positions are now invalid, clear everything and start over.
87         notifyDataSetChanged();
88     }
89 
90     /**
91      * Sets the first day of the week.
92      *
93      * @param weekStart which day the week should start on, valid values are
94      *                  {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
95      */
setFirstDayOfWeek(int weekStart)96     public void setFirstDayOfWeek(int weekStart) {
97         mFirstDayOfWeek = weekStart;
98 
99         // Update displayed views.
100         final int count = mItems.size();
101         for (int i = 0; i < count; i++) {
102             final SimpleMonthView monthView = mItems.valueAt(i).calendar;
103             monthView.setFirstDayOfWeek(weekStart);
104         }
105     }
106 
getFirstDayOfWeek()107     public int getFirstDayOfWeek() {
108         return mFirstDayOfWeek;
109     }
110 
getBoundsForDate(Calendar day, Rect outBounds)111     public boolean getBoundsForDate(Calendar day, Rect outBounds) {
112         final int position = getPositionForDay(day);
113         final ViewHolder monthView = mItems.get(position, null);
114         if (monthView == null) {
115             return false;
116         } else {
117             final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH);
118             return monthView.calendar.getBoundsForDay(dayOfMonth, outBounds);
119         }
120     }
121 
122     /**
123      * Sets the selected day.
124      *
125      * @param day the selected day
126      */
setSelectedDay(@ullable Calendar day)127     public void setSelectedDay(@Nullable Calendar day) {
128         final int oldPosition = getPositionForDay(mSelectedDay);
129         final int newPosition = getPositionForDay(day);
130 
131         // Clear the old position if necessary.
132         if (oldPosition != newPosition && oldPosition >= 0) {
133             final ViewHolder oldMonthView = mItems.get(oldPosition, null);
134             if (oldMonthView != null) {
135                 oldMonthView.calendar.setSelectedDay(-1);
136             }
137         }
138 
139         // Set the new position.
140         if (newPosition >= 0) {
141             final ViewHolder newMonthView = mItems.get(newPosition, null);
142             if (newMonthView != null) {
143                 final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH);
144                 newMonthView.calendar.setSelectedDay(dayOfMonth);
145             }
146         }
147 
148         mSelectedDay = day;
149     }
150 
151     /**
152      * Sets the listener to call when the user selects a day.
153      *
154      * @param listener The listener to call.
155      */
setOnDaySelectedListener(OnDaySelectedListener listener)156     public void setOnDaySelectedListener(OnDaySelectedListener listener) {
157         mOnDaySelectedListener = listener;
158     }
159 
setCalendarTextColor(ColorStateList calendarTextColor)160     void setCalendarTextColor(ColorStateList calendarTextColor) {
161         mCalendarTextColor = calendarTextColor;
162         notifyDataSetChanged();
163     }
164 
setDaySelectorColor(ColorStateList selectorColor)165     void setDaySelectorColor(ColorStateList selectorColor) {
166         mDaySelectorColor = selectorColor;
167         notifyDataSetChanged();
168     }
169 
setMonthTextAppearance(int resId)170     void setMonthTextAppearance(int resId) {
171         mMonthTextAppearance = resId;
172         notifyDataSetChanged();
173     }
174 
setDayOfWeekTextAppearance(int resId)175     void setDayOfWeekTextAppearance(int resId) {
176         mDayOfWeekTextAppearance = resId;
177         notifyDataSetChanged();
178     }
179 
getDayOfWeekTextAppearance()180     int getDayOfWeekTextAppearance() {
181         return mDayOfWeekTextAppearance;
182     }
183 
setDayTextAppearance(int resId)184     void setDayTextAppearance(int resId) {
185         mDayTextAppearance = resId;
186         notifyDataSetChanged();
187     }
188 
getDayTextAppearance()189     int getDayTextAppearance() {
190         return mDayTextAppearance;
191     }
192 
193     @Override
getCount()194     public int getCount() {
195         return mCount;
196     }
197 
198     @Override
isViewFromObject(View view, Object object)199     public boolean isViewFromObject(View view, Object object) {
200         final ViewHolder holder = (ViewHolder) object;
201         return view == holder.container;
202     }
203 
getMonthForPosition(int position)204     private int getMonthForPosition(int position) {
205         return (position + mMinDate.get(Calendar.MONTH)) % MONTHS_IN_YEAR;
206     }
207 
getYearForPosition(int position)208     private int getYearForPosition(int position) {
209         final int yearOffset = (position + mMinDate.get(Calendar.MONTH)) / MONTHS_IN_YEAR;
210         return yearOffset + mMinDate.get(Calendar.YEAR);
211     }
212 
getPositionForDay(@ullable Calendar day)213     private int getPositionForDay(@Nullable Calendar day) {
214         if (day == null) {
215             return -1;
216         }
217 
218         final int yearOffset = day.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
219         final int monthOffset = day.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
220         final int position = yearOffset * MONTHS_IN_YEAR + monthOffset;
221         return position;
222     }
223 
224     @Override
instantiateItem(ViewGroup container, int position)225     public Object instantiateItem(ViewGroup container, int position) {
226         final View itemView = mInflater.inflate(mLayoutResId, container, false);
227 
228         final SimpleMonthView v = itemView.findViewById(mCalendarViewId);
229         v.setOnDayClickListener(mOnDayClickListener);
230         v.setMonthTextAppearance(mMonthTextAppearance);
231         v.setDayOfWeekTextAppearance(mDayOfWeekTextAppearance);
232         v.setDayTextAppearance(mDayTextAppearance);
233 
234         if (mDaySelectorColor != null) {
235             v.setDaySelectorColor(mDaySelectorColor);
236         }
237 
238         if (mDayHighlightColor != null) {
239             v.setDayHighlightColor(mDayHighlightColor);
240         }
241 
242         if (mCalendarTextColor != null) {
243             v.setMonthTextColor(mCalendarTextColor);
244             v.setDayOfWeekTextColor(mCalendarTextColor);
245             v.setDayTextColor(mCalendarTextColor);
246         }
247 
248         final int month = getMonthForPosition(position);
249         final int year = getYearForPosition(position);
250 
251         final int selectedDay;
252         if (mSelectedDay != null && mSelectedDay.get(Calendar.MONTH) == month && mSelectedDay.get(
253                 Calendar.YEAR) == year) {
254             selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH);
255         } else {
256             selectedDay = -1;
257         }
258 
259         final int enabledDayRangeStart;
260         if (mMinDate.get(Calendar.MONTH) == month && mMinDate.get(Calendar.YEAR) == year) {
261             enabledDayRangeStart = mMinDate.get(Calendar.DAY_OF_MONTH);
262         } else {
263             enabledDayRangeStart = 1;
264         }
265 
266         final int enabledDayRangeEnd;
267         if (mMaxDate.get(Calendar.MONTH) == month && mMaxDate.get(Calendar.YEAR) == year) {
268             enabledDayRangeEnd = mMaxDate.get(Calendar.DAY_OF_MONTH);
269         } else {
270             enabledDayRangeEnd = 31;
271         }
272 
273         v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek,
274                 enabledDayRangeStart, enabledDayRangeEnd);
275 
276         final ViewHolder holder = new ViewHolder(position, itemView, v);
277         mItems.put(position, holder);
278 
279         container.addView(itemView);
280 
281         return holder;
282     }
283 
284     @Override
destroyItem(ViewGroup container, int position, Object object)285     public void destroyItem(ViewGroup container, int position, Object object) {
286         final ViewHolder holder = (ViewHolder) object;
287         container.removeView(holder.container);
288 
289         mItems.remove(position);
290     }
291 
292     @Override
getItemPosition(Object object)293     public int getItemPosition(Object object) {
294         final ViewHolder holder = (ViewHolder) object;
295         return holder.position;
296     }
297 
298     @Override
getPageTitle(int position)299     public CharSequence getPageTitle(int position) {
300         final SimpleMonthView v = mItems.get(position).calendar;
301         if (v != null) {
302             return v.getMonthYearLabel();
303         }
304         return null;
305     }
306 
getView(Object object)307     SimpleMonthView getView(Object object) {
308         if (object == null) {
309             return null;
310         }
311         final ViewHolder holder = (ViewHolder) object;
312         return holder.calendar;
313     }
314 
315     private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() {
316         @Override
317         public void onDayClick(SimpleMonthView view, Calendar day) {
318             if (day != null) {
319                 setSelectedDay(day);
320 
321                 if (mOnDaySelectedListener != null) {
322                     mOnDaySelectedListener.onDaySelected(DayPickerPagerAdapter.this, day);
323                 }
324             }
325         }
326     };
327 
328     private static class ViewHolder {
329         public final int position;
330         public final View container;
331         public final SimpleMonthView calendar;
332 
ViewHolder(int position, View container, SimpleMonthView calendar)333         public ViewHolder(int position, View container, SimpleMonthView calendar) {
334             this.position = position;
335             this.container = container;
336             this.calendar = calendar;
337         }
338     }
339 
340     public interface OnDaySelectedListener {
onDaySelected(DayPickerPagerAdapter view, Calendar day)341         public void onDaySelected(DayPickerPagerAdapter view, Calendar day);
342     }
343 }
344