1 /*
2  * Copyright (C) 2021 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 package com.android.calendar.month
17 // TODO Remove calendar imports when the required methods have been
18 // refactored into the public api
19 import com.android.calendar.CalendarController
20 import com.android.calendar.Utils
21 import android.content.Context
22 import android.text.format.Time
23 import android.util.Log
24 import android.view.GestureDetector
25 import android.view.MotionEvent
26 import android.view.View
27 import android.view.View.OnTouchListener
28 import android.view.ViewGroup
29 import android.widget.AbsListView.LayoutParams
30 import android.widget.BaseAdapter
31 import android.widget.ListView
32 import java.util.Calendar
33 import java.util.HashMap
34 import java.util.Locale
35 
36 /**
37  *
38  *
39  * This is a specialized adapter for creating a list of weeks with selectable
40  * days. It can be configured to display the week number, start the week on a
41  * given day, show a reduced number of days, or display an arbitrary number of
42  * weeks at a time. See [SimpleDayPickerFragment] for usage.
43  *
44  */
45 open class SimpleWeeksAdapter(context: Context, params: HashMap<String?, Int?>?) : BaseAdapter(),
46     OnTouchListener {
47     protected var mContext: Context
48 
49     // The day to highlight as selected
50     protected var mSelectedDay: Time? = null
51 
52     // The week since 1970 that the selected day is in
53     protected var mSelectedWeek = 0
54 
55     // When the week starts; numbered like Time.<WEEKDAY> (e.g. SUNDAY=0).
56     protected var mFirstDayOfWeek: Int
57     protected var mShowWeekNumber = false
58     protected var mGestureDetector: GestureDetector? = null
59     protected var mNumWeeks = DEFAULT_NUM_WEEKS
60     protected var mDaysPerWeek = DEFAULT_DAYS_PER_WEEK
61     protected var mFocusMonth = DEFAULT_MONTH_FOCUS
62 
63     /**
64      * Set up the gesture detector and selected time
65      */
initnull66     protected open fun init() {
67         mGestureDetector = GestureDetector(mContext, CalendarGestureListener())
68         mSelectedDay = Time()
69         mSelectedDay?.setToNow()
70     }
71 
72     /**
73      * Parse the parameters and set any necessary fields. See
74      * [.WEEK_PARAMS_NUM_WEEKS] for parameter details.
75      *
76      * @param params A list of parameters for this adapter
77      */
updateParamsnull78     fun updateParams(params: HashMap<String?, Int?>?) {
79         if (params == null) {
80             Log.e(TAG, "WeekParameters are null! Cannot update adapter.")
81             return
82         }
83         if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
84             // Casting from Int? --> Int
85             mFocusMonth = params.get(WEEK_PARAMS_FOCUS_MONTH) as Int
86         }
87         if (params.containsKey(WEEK_PARAMS_FOCUS_MONTH)) {
88             // Casting from Int? --> Int
89             mNumWeeks = params.get(WEEK_PARAMS_NUM_WEEKS) as Int
90         }
91         if (params.containsKey(WEEK_PARAMS_SHOW_WEEK)) {
92             // Casting from Int? --> Int
93             mShowWeekNumber = params.get(WEEK_PARAMS_SHOW_WEEK) as Int != 0
94         }
95         if (params.containsKey(WEEK_PARAMS_WEEK_START)) {
96             // Casting from Int? --> Int
97             mFirstDayOfWeek = params.get(WEEK_PARAMS_WEEK_START) as Int
98         }
99         if (params.containsKey(WEEK_PARAMS_JULIAN_DAY)) {
100             // Casting from Int? --> Int
101             val julianDay: Int = params.get(WEEK_PARAMS_JULIAN_DAY) as Int
102             mSelectedDay?.setJulianDay(julianDay)
103             mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(julianDay, mFirstDayOfWeek)
104         }
105         if (params.containsKey(WEEK_PARAMS_DAYS_PER_WEEK)) {
106             // Casting from Int? --> Int
107             mDaysPerWeek = params.get(WEEK_PARAMS_DAYS_PER_WEEK) as Int
108         }
109         refresh()
110     }
111 
112     /**
113      * Updates the selected day and related parameters.
114      *
115      * @param selectedTime The time to highlight
116      */
setSelectedDaynull117     open fun setSelectedDay(selectedTime: Time?) {
118         mSelectedDay?.set(selectedTime)
119         val millis: Long = mSelectedDay!!.normalize(true)
120         mSelectedWeek = Utils.getWeeksSinceEpochFromJulianDay(
121             Time.getJulianDay(millis, mSelectedDay!!.gmtoff), mFirstDayOfWeek
122         )
123         notifyDataSetChanged()
124     }
125 
126     /**
127      * Returns the currently highlighted day
128      *
129      * @return
130      */
getSelectedDaynull131     fun getSelectedDay(): Time? {
132         return mSelectedDay
133     }
134 
135     /**
136      * updates any config options that may have changed and refreshes the view
137      */
refreshnull138     internal open fun refresh() {
139         notifyDataSetChanged()
140     }
141 
142     @Override
getCountnull143     override fun getCount(): Int {
144         return WEEK_COUNT
145     }
146 
147     @Override
getItemnull148     override fun getItem(position: Int): Any? {
149         return null
150     }
151 
152     @Override
getItemIdnull153     override fun getItemId(position: Int): Long {
154         return position.toLong()
155     }
156 
157     @SuppressWarnings("unchecked")
158     @Override
getViewnull159     override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
160         val v: SimpleWeekView
161         var drawingParams: HashMap<String?, Int?>? = null
162         if (convertView != null) {
163             v = convertView as SimpleWeekView
164             // We store the drawing parameters in the view so it can be recycled
165             drawingParams = v.getTag() as HashMap<String?, Int?>
166         } else {
167             v = SimpleWeekView(mContext)
168             // Set up the new view
169             val params = LayoutParams(
170                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT
171             )
172             v.setLayoutParams(params)
173             v.setClickable(true)
174             v.setOnTouchListener(this)
175         }
176         if (drawingParams == null) {
177             drawingParams = HashMap<String?, Int?>()
178         }
179         drawingParams.clear()
180         var selectedDay = -1
181         if (mSelectedWeek == position) {
182             selectedDay = mSelectedDay!!.weekDay
183         }
184 
185         // pass in all the view parameters
186         drawingParams.put(
187             SimpleWeekView.VIEW_PARAMS_HEIGHT,
188             (parent.getHeight() - WEEK_7_OVERHANG_HEIGHT) / mNumWeeks
189         )
190         drawingParams.put(SimpleWeekView.VIEW_PARAMS_SELECTED_DAY, selectedDay)
191         drawingParams.put(SimpleWeekView.VIEW_PARAMS_SHOW_WK_NUM, if (mShowWeekNumber) 1 else 0)
192         drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek)
193         drawingParams.put(SimpleWeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek)
194         drawingParams.put(SimpleWeekView.VIEW_PARAMS_WEEK, position)
195         drawingParams.put(SimpleWeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth)
196         v.setWeekParams(drawingParams, mSelectedDay!!.timezone)
197         v.invalidate()
198         return v
199     }
200 
201     /**
202      * Changes which month is in focus and updates the view.
203      *
204      * @param month The month to show as in focus [0-11]
205      */
updateFocusMonthnull206     fun updateFocusMonth(month: Int) {
207         mFocusMonth = month
208         notifyDataSetChanged()
209     }
210 
211     @Override
onTouchnull212     override fun onTouch(v: View, event: MotionEvent): Boolean {
213         if (mGestureDetector!!.onTouchEvent(event)) {
214             val view: SimpleWeekView = v as SimpleWeekView
215             val day: Time? = (v as SimpleWeekView).getDayFromLocation(event.getX())
216             if (Log.isLoggable(TAG, Log.DEBUG)) {
217                 Log.d(TAG, "Touched day at Row=" + view.mWeek.toString() + " day=" +
218                     day?.toString())
219             }
220             if (day != null) {
221                 onDayTapped(day)
222             }
223             return true
224         }
225         return false
226     }
227 
228     /**
229      * Maintains the same hour/min/sec but moves the day to the tapped day.
230      *
231      * @param day The day that was tapped
232      */
onDayTappednull233     protected open fun onDayTapped(day: Time) {
234         day.hour = mSelectedDay!!.hour
235         day.minute = mSelectedDay!!.minute
236         day.second = mSelectedDay!!.second
237         setSelectedDay(day)
238     }
239 
240     /**
241      * This is here so we can identify single tap events and set the selected
242      * day correctly
243      */
244     protected inner class CalendarGestureListener : GestureDetector.SimpleOnGestureListener() {
245         @Override
onSingleTapUpnull246         override fun onSingleTapUp(e: MotionEvent): Boolean {
247             return true
248         }
249     }
250 
251     var mListView: ListView? = null
setListViewnull252     fun setListView(lv: ListView?) {
253         mListView = lv
254     }
255 
256     companion object {
257         private const val TAG = "MonthByWeek"
258 
259         /**
260          * The number of weeks to display at a time.
261          */
262         const val WEEK_PARAMS_NUM_WEEKS = "num_weeks"
263 
264         /**
265          * Which month should be in focus currently.
266          */
267         const val WEEK_PARAMS_FOCUS_MONTH = "focus_month"
268 
269         /**
270          * Whether the week number should be shown. Non-zero to show them.
271          */
272         const val WEEK_PARAMS_SHOW_WEEK = "week_numbers"
273 
274         /**
275          * Which day the week should start on. [Time.SUNDAY] through
276          * [Time.SATURDAY].
277          */
278         const val WEEK_PARAMS_WEEK_START = "week_start"
279 
280         /**
281          * The Julian day to highlight as selected.
282          */
283         const val WEEK_PARAMS_JULIAN_DAY = "selected_day"
284 
285         /**
286          * How many days of the week to display [1-7].
287          */
288         const val WEEK_PARAMS_DAYS_PER_WEEK = "days_per_week"
289         protected const val WEEK_COUNT = CalendarController.MAX_CALENDAR_WEEK -
290             CalendarController.MIN_CALENDAR_WEEK
291         protected var DEFAULT_NUM_WEEKS = 6
292         protected var DEFAULT_MONTH_FOCUS = 0
293         protected var DEFAULT_DAYS_PER_WEEK = 7
294         protected var DEFAULT_WEEK_HEIGHT = 32
295         protected var WEEK_7_OVERHANG_HEIGHT = 7
296         protected var mScale = 0f
297     }
298 
299     init {
300         mContext = context
301 
302         // Get default week start based on locale, subtracting one for use with android Time.
303         val cal: Calendar = Calendar.getInstance(Locale.getDefault())
304         mFirstDayOfWeek = cal.getFirstDayOfWeek() - 1
305         if (mScale == 0f) {
306             mScale = context.getResources().getDisplayMetrics().density
307             if (mScale != 1f) {
308                 WEEK_7_OVERHANG_HEIGHT *= mScale.toInt()
309             }
310         }
311         init()
312         updateParams(params)
313     }
314 }