1/*
2 * Copyright (C) 2014 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.R;
20
21import android.content.Context;
22import android.content.res.ColorStateList;
23import android.content.res.TypedArray;
24import android.graphics.Color;
25import android.view.View;
26import android.view.ViewGroup;
27import android.widget.SimpleMonthView.OnDayClickListener;
28
29import java.util.Calendar;
30
31/**
32 * An adapter for a list of {@link android.widget.SimpleMonthView} items.
33 */
34class SimpleMonthAdapter extends BaseAdapter {
35    private final Calendar mMinDate = Calendar.getInstance();
36    private final Calendar mMaxDate = Calendar.getInstance();
37
38    private final Context mContext;
39
40    private Calendar mSelectedDay = Calendar.getInstance();
41    private ColorStateList mCalendarTextColors = ColorStateList.valueOf(Color.BLACK);
42    private OnDaySelectedListener mOnDaySelectedListener;
43
44    private int mFirstDayOfWeek;
45
46    public SimpleMonthAdapter(Context context) {
47        mContext = context;
48    }
49
50    public void setRange(Calendar min, Calendar max) {
51        mMinDate.setTimeInMillis(min.getTimeInMillis());
52        mMaxDate.setTimeInMillis(max.getTimeInMillis());
53
54        notifyDataSetInvalidated();
55    }
56
57    public void setFirstDayOfWeek(int firstDayOfWeek) {
58        mFirstDayOfWeek = firstDayOfWeek;
59
60        notifyDataSetInvalidated();
61    }
62
63    public int getFirstDayOfWeek() {
64        return mFirstDayOfWeek;
65    }
66
67    /**
68     * Updates the selected day and related parameters.
69     *
70     * @param day The day to highlight
71     */
72    public void setSelectedDay(Calendar day) {
73        mSelectedDay = day;
74
75        notifyDataSetChanged();
76    }
77
78    /**
79     * Sets the listener to call when the user selects a day.
80     *
81     * @param listener The listener to call.
82     */
83    public void setOnDaySelectedListener(OnDaySelectedListener listener) {
84        mOnDaySelectedListener = listener;
85    }
86
87    void setCalendarTextColor(ColorStateList colors) {
88        mCalendarTextColors = colors;
89    }
90
91    /**
92     * Sets the text color, size, style, hint color, and highlight color from
93     * the specified TextAppearance resource. This is mostly copied from
94     * {@link TextView#setTextAppearance(Context, int)}.
95     */
96    void setCalendarTextAppearance(int resId) {
97        final TypedArray a = mContext.obtainStyledAttributes(resId, R.styleable.TextAppearance);
98
99        final ColorStateList textColor = a.getColorStateList(R.styleable.TextAppearance_textColor);
100        if (textColor != null) {
101            mCalendarTextColors = textColor;
102        }
103
104        // TODO: Support font size, etc.
105
106        a.recycle();
107    }
108
109    @Override
110    public int getCount() {
111        final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
112        final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
113        return diffMonth + 12 * diffYear + 1;
114    }
115
116    @Override
117    public Object getItem(int position) {
118        return null;
119    }
120
121    @Override
122    public long getItemId(int position) {
123        return position;
124    }
125
126    @Override
127    public boolean hasStableIds() {
128        return true;
129    }
130
131    @SuppressWarnings("unchecked")
132    @Override
133    public View getView(int position, View convertView, ViewGroup parent) {
134        final SimpleMonthView v;
135        if (convertView != null) {
136            v = (SimpleMonthView) convertView;
137        } else {
138            v = new SimpleMonthView(mContext);
139
140            // Set up the new view
141            final AbsListView.LayoutParams params = new AbsListView.LayoutParams(
142                    AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.MATCH_PARENT);
143            v.setLayoutParams(params);
144            v.setClickable(true);
145            v.setOnDayClickListener(mOnDayClickListener);
146
147            if (mCalendarTextColors != null) {
148                v.setTextColor(mCalendarTextColors);
149            }
150        }
151
152        final int minMonth = mMinDate.get(Calendar.MONTH);
153        final int minYear = mMinDate.get(Calendar.YEAR);
154        final int currentMonth = position + minMonth;
155        final int month = currentMonth % 12;
156        final int year = currentMonth / 12 + minYear;
157        final int selectedDay;
158        if (isSelectedDayInMonth(year, month)) {
159            selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH);
160        } else {
161            selectedDay = -1;
162        }
163
164        // Invokes requestLayout() to ensure that the recycled view is set with the appropriate
165        // height/number of weeks before being displayed.
166        v.reuse();
167
168        final int enabledDayRangeStart;
169        if (minMonth == month && minYear == year) {
170            enabledDayRangeStart = mMinDate.get(Calendar.DAY_OF_MONTH);
171        } else {
172            enabledDayRangeStart = 1;
173        }
174
175        final int enabledDayRangeEnd;
176        if (mMaxDate.get(Calendar.MONTH) == month && mMaxDate.get(Calendar.YEAR) == year) {
177            enabledDayRangeEnd = mMaxDate.get(Calendar.DAY_OF_MONTH);
178        } else {
179            enabledDayRangeEnd = 31;
180        }
181
182        v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek,
183                enabledDayRangeStart, enabledDayRangeEnd);
184        v.invalidate();
185
186        return v;
187    }
188
189    private boolean isSelectedDayInMonth(int year, int month) {
190        return mSelectedDay.get(Calendar.YEAR) == year && mSelectedDay.get(Calendar.MONTH) == month;
191    }
192
193    private boolean isCalendarInRange(Calendar value) {
194        return value.compareTo(mMinDate) >= 0 && value.compareTo(mMaxDate) <= 0;
195    }
196
197    private final OnDayClickListener mOnDayClickListener = new OnDayClickListener() {
198        @Override
199        public void onDayClick(SimpleMonthView view, Calendar day) {
200            if (day != null && isCalendarInRange(day)) {
201                setSelectedDay(day);
202
203                if (mOnDaySelectedListener != null) {
204                    mOnDaySelectedListener.onDaySelected(SimpleMonthAdapter.this, day);
205                }
206            }
207        }
208    };
209
210    public interface OnDaySelectedListener {
211        public void onDaySelected(SimpleMonthAdapter view, Calendar day);
212    }
213}
214