blob: 2fe1027208a2cec9a5140f814dbfcdd95d2d65b9 [file] [log] [blame]
/*
* 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)
}
}
}