| /* |
| * Copyright (C) 2021 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.android.calendar |
| |
| import com.android.calendar.CalendarController.ViewType |
| import android.content.Context |
| import android.os.Handler |
| import android.text.format.DateUtils |
| import android.text.format.Time |
| import android.view.LayoutInflater |
| import android.view.View |
| import android.view.ViewGroup |
| import android.widget.BaseAdapter |
| import android.widget.TextView |
| import java.util.Formatter |
| import java.util.Locale |
| |
| /* |
| * The MenuSpinnerAdapter defines the look of the ActionBar's pull down menu |
| * for small screen layouts. The pull down menu replaces the tabs uses for big screen layouts |
| * |
| * The MenuSpinnerAdapter responsible for creating the views used for in the pull down menu. |
| */ |
| class CalendarViewAdapter(context: Context, viewType: Int, showDate: Boolean) : BaseAdapter() { |
| private val mButtonNames: Array<String> // Text on buttons |
| |
| // Used to define the look of the menu button according to the current view: |
| // Day view: show day of the week + full date underneath |
| // Week view: show the month + year |
| // Month view: show the month + year |
| // Agenda view: show day of the week + full date underneath |
| private var mCurrentMainView: Int |
| private val mInflater: LayoutInflater |
| |
| // The current selected event's time, used to calculate the date and day of the week |
| // for the buttons. |
| private var mMilliTime: Long = 0 |
| private var mTimeZone: String? = null |
| private var mTodayJulianDay: Long = 0 |
| private val mContext: Context = context |
| private val mFormatter: Formatter |
| private val mStringBuilder: StringBuilder |
| private var mMidnightHandler: Handler? = null // Used to run a time update every midnight |
| private val mShowDate: Boolean // Spinner mode indicator (view name or view name with date) |
| |
| // Updates time specific variables (time-zone, today's Julian day). |
| private val mTimeUpdater: Runnable = object : Runnable { |
| @Override |
| override fun run() { |
| refresh(mContext) |
| } |
| } |
| |
| // Sets the time zone and today's Julian day to be used by the adapter. |
| // Also, notify listener on the change and resets the midnight update thread. |
| fun refresh(context: Context?) { |
| mTimeZone = Utils.getTimeZone(context, mTimeUpdater) |
| val time = Time(mTimeZone) |
| val now: Long = System.currentTimeMillis() |
| time.set(now) |
| mTodayJulianDay = Time.getJulianDay(now, time.gmtoff).toLong() |
| notifyDataSetChanged() |
| setMidnightHandler() |
| } |
| |
| // Sets a thread to run 1 second after midnight and update the current date |
| // This is used to display correctly the date of yesterday/today/tomorrow |
| private fun setMidnightHandler() { |
| mMidnightHandler?.removeCallbacks(mTimeUpdater) |
| // Set the time updater to run at 1 second after midnight |
| val now: Long = System.currentTimeMillis() |
| val time = Time(mTimeZone) |
| time.set(now) |
| val runInMillis: Long = ((24 * 3600 - time.hour * 3600 - time.minute * 60 - |
| time.second + 1) * 1000).toLong() |
| mMidnightHandler?.postDelayed(mTimeUpdater, runInMillis) |
| } |
| |
| // Stops the midnight update thread, called by the activity when it is paused. |
| fun onPause() { |
| mMidnightHandler?.removeCallbacks(mTimeUpdater) |
| } |
| |
| // Returns the amount of buttons in the menu |
| @Override |
| override fun getCount(): Int { |
| return mButtonNames.size |
| } |
| |
| @Override |
| override fun getItem(position: Int): Any? { |
| return if (position < mButtonNames.size) { |
| mButtonNames[position] |
| } else null |
| } |
| |
| @Override |
| override fun getItemId(position: Int): Long { |
| // Item ID is its location in the list |
| return position.toLong() |
| } |
| |
| @Override |
| override fun hasStableIds(): Boolean { |
| return false |
| } |
| |
| @Override |
| override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? { |
| var v: View? |
| if (mShowDate) { |
| // Check if can recycle the view |
| if (convertView == null || (convertView.getTag() as Int) |
| != R.layout.actionbar_pulldown_menu_top_button as Int) { |
| v = mInflater.inflate(R.layout.actionbar_pulldown_menu_top_button, parent, false) |
| // Set the tag to make sure you can recycle it when you get it |
| // as a convert view |
| v.setTag(Integer(R.layout.actionbar_pulldown_menu_top_button)) |
| } else { |
| v = convertView |
| } |
| val weekDay: TextView = v?.findViewById(R.id.top_button_weekday) as TextView |
| val date: TextView = v?.findViewById(R.id.top_button_date) as TextView |
| when (mCurrentMainView) { |
| ViewType.DAY -> { |
| weekDay.setVisibility(View.VISIBLE) |
| weekDay.setText(buildDayOfWeek()) |
| date.setText(buildFullDate()) |
| } |
| ViewType.WEEK -> { |
| if (Utils.getShowWeekNumber(mContext)) { |
| weekDay.setVisibility(View.VISIBLE) |
| weekDay.setText(buildWeekNum()) |
| } else { |
| weekDay.setVisibility(View.GONE) |
| } |
| date.setText(buildMonthYearDate()) |
| } |
| ViewType.MONTH -> { |
| weekDay.setVisibility(View.GONE) |
| date.setText(buildMonthYearDate()) |
| } |
| else -> v = null |
| } |
| } else { |
| if (convertView == null || (convertView.getTag() as Int) |
| != R.layout.actionbar_pulldown_menu_top_button_no_date as Int) { |
| v = mInflater.inflate( |
| R.layout.actionbar_pulldown_menu_top_button_no_date, parent, false) |
| // Set the tag to make sure you can recycle it when you get it |
| // as a convert view |
| v.setTag(Integer(R.layout.actionbar_pulldown_menu_top_button_no_date)) |
| } else { |
| v = convertView |
| } |
| val title: TextView? = v as TextView? |
| when (mCurrentMainView) { |
| ViewType.DAY -> title?.setText(mButtonNames[DAY_BUTTON_INDEX]) |
| ViewType.WEEK -> title?.setText(mButtonNames[WEEK_BUTTON_INDEX]) |
| ViewType.MONTH -> title?.setText(mButtonNames[MONTH_BUTTON_INDEX]) |
| else -> v = null |
| } |
| } |
| return v |
| } |
| |
| @Override |
| override fun getItemViewType(position: Int): Int { |
| // Only one kind of view is used |
| return BUTTON_VIEW_TYPE |
| } |
| |
| @Override |
| override fun getViewTypeCount(): Int { |
| return VIEW_TYPE_NUM |
| } |
| |
| @Override |
| override fun isEmpty(): Boolean { |
| return mButtonNames.size == 0 |
| } |
| |
| @Override |
| override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View? { |
| var v: View? = mInflater.inflate(R.layout.actionbar_pulldown_menu_button, parent, false) |
| val viewType: TextView? = v?.findViewById(R.id.button_view) as? TextView |
| val date: TextView? = v?.findViewById(R.id.button_date) as? TextView |
| when (position) { |
| DAY_BUTTON_INDEX -> { |
| viewType?.setText(mButtonNames[DAY_BUTTON_INDEX]) |
| if (mShowDate) { |
| date?.setText(buildMonthDayDate()) |
| } |
| } |
| WEEK_BUTTON_INDEX -> { |
| viewType?.setText(mButtonNames[WEEK_BUTTON_INDEX]) |
| if (mShowDate) { |
| date?.setText(buildWeekDate()) |
| } |
| } |
| MONTH_BUTTON_INDEX -> { |
| viewType?.setText(mButtonNames[MONTH_BUTTON_INDEX]) |
| if (mShowDate) { |
| date?.setText(buildMonthDate()) |
| } |
| } |
| else -> v = convertView |
| } |
| return v |
| } |
| |
| // Updates the current viewType |
| // Used to match the label on the menu button with the calendar view |
| fun setMainView(viewType: Int) { |
| mCurrentMainView = viewType |
| notifyDataSetChanged() |
| } |
| |
| // Update the date that is displayed on buttons |
| // Used when the user selects a new day/week/month to watch |
| fun setTime(time: Long) { |
| mMilliTime = time |
| notifyDataSetChanged() |
| } |
| |
| // Builds a string with the day of the week and the word yesterday/today/tomorrow |
| // before it if applicable. |
| private fun buildDayOfWeek(): String { |
| val t = Time(mTimeZone) |
| t.set(mMilliTime) |
| val julianDay: Long = Time.getJulianDay(mMilliTime, t.gmtoff).toLong() |
| var dayOfWeek: String? = null |
| mStringBuilder.setLength(0) |
| dayOfWeek = if (julianDay == mTodayJulianDay) { |
| mContext.getString(R.string.agenda_today, |
| DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, |
| DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()) |
| } else if (julianDay == mTodayJulianDay - 1) { |
| mContext.getString(R.string.agenda_yesterday, |
| DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, |
| DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()) |
| } else if (julianDay == mTodayJulianDay + 1) { |
| mContext.getString(R.string.agenda_tomorrow, |
| DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, |
| DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString()) |
| } else { |
| DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, |
| DateUtils.FORMAT_SHOW_WEEKDAY, mTimeZone).toString() |
| } |
| return dayOfWeek.toUpperCase() |
| } |
| |
| // Builds strings with different formats: |
| // Full date: Month,day Year |
| // Month year |
| // Month day |
| // Month |
| // Week: month day-day or month day - month day |
| private fun buildFullDate(): String { |
| mStringBuilder.setLength(0) |
| return DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, |
| DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString() |
| } |
| |
| private fun buildMonthYearDate(): String { |
| mStringBuilder.setLength(0) |
| return DateUtils.formatDateRange( |
| mContext, |
| mFormatter, |
| mMilliTime, |
| mMilliTime, |
| DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_MONTH_DAY |
| or DateUtils.FORMAT_SHOW_YEAR, mTimeZone).toString() |
| } |
| |
| private fun buildMonthDayDate(): String { |
| mStringBuilder.setLength(0) |
| return DateUtils.formatDateRange(mContext, mFormatter, mMilliTime, mMilliTime, |
| DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR, mTimeZone).toString() |
| } |
| |
| private fun buildMonthDate(): String { |
| mStringBuilder.setLength(0) |
| return DateUtils.formatDateRange( |
| mContext, |
| mFormatter, |
| mMilliTime, |
| mMilliTime, |
| DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR |
| or DateUtils.FORMAT_NO_MONTH_DAY, mTimeZone).toString() |
| } |
| |
| private fun buildWeekDate(): String { |
| // Calculate the start of the week, taking into account the "first day of the week" |
| // setting. |
| val t = Time(mTimeZone) |
| t.set(mMilliTime) |
| val firstDayOfWeek: Int = Utils.getFirstDayOfWeek(mContext) |
| val dayOfWeek: Int = t.weekDay |
| var diff = dayOfWeek - firstDayOfWeek |
| if (diff != 0) { |
| if (diff < 0) { |
| diff += 7 |
| } |
| t.monthDay -= diff |
| t.normalize(true /* ignore isDst */) |
| } |
| val weekStartTime: Long = t.toMillis(true) |
| // The end of the week is 6 days after the start of the week |
| val weekEndTime: Long = weekStartTime + DateUtils.WEEK_IN_MILLIS - DateUtils.DAY_IN_MILLIS |
| |
| // If week start and end is in 2 different months, use short months names |
| val t1 = Time(mTimeZone) |
| t.set(weekEndTime) |
| var flags: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR |
| if (t.month !== t1.month) { |
| flags = flags or DateUtils.FORMAT_ABBREV_MONTH |
| } |
| mStringBuilder.setLength(0) |
| return DateUtils.formatDateRange(mContext, mFormatter, weekStartTime, |
| weekEndTime, flags, mTimeZone).toString() |
| } |
| |
| private fun buildWeekNum(): String { |
| val week: Int = Utils.getWeekNumberFromTime(mMilliTime, mContext) |
| return mContext.getResources().getQuantityString(R.plurals.weekN, week, week) |
| } |
| |
| companion object { |
| private const val TAG = "MenuSpinnerAdapter" |
| |
| // Defines the types of view returned by this spinner |
| private const val BUTTON_VIEW_TYPE = 0 |
| const val VIEW_TYPE_NUM = 1 // Increase this if you add more view types |
| const val DAY_BUTTON_INDEX = 0 |
| const val WEEK_BUTTON_INDEX = 1 |
| const val MONTH_BUTTON_INDEX = 2 |
| const val AGENDA_BUTTON_INDEX = 3 |
| } |
| |
| init { |
| mMidnightHandler = Handler() |
| mCurrentMainView = viewType |
| mShowDate = showDate |
| |
| // Initialize |
| mButtonNames = context.getResources().getStringArray(R.array.buttons_list) |
| mInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater |
| mStringBuilder = StringBuilder(50) |
| mFormatter = Formatter(mStringBuilder, Locale.getDefault()) |
| |
| // Sets time specific variables and starts a thread for midnight updates |
| if (showDate) { |
| refresh(context) |
| } |
| } |
| } |