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