blob: c1561d72737244829a39493bde79ae9b1e2b0a0c [file] [log] [blame]
/*
* Copyright (C) 2020 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.deskclock.uidata
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Typeface
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.android.deskclock.AlarmClockFragment
import com.android.deskclock.ClockFragment
import com.android.deskclock.R
import com.android.deskclock.Utils
import com.android.deskclock.stopwatch.StopwatchFragment
import com.android.deskclock.timer.TimerFragment
/**
* All application-wide user interface data is accessible through this singleton.
*/
class UiDataModel private constructor() {
/** Identifies each of the primary tabs within the application. */
enum class Tab(
fragmentClass: Class<*>,
@DrawableRes val iconResId: Int,
@StringRes val labelResId: Int
) {
ALARMS(AlarmClockFragment::class.java, R.drawable.ic_tab_alarm, R.string.menu_alarm),
CLOCKS(ClockFragment::class.java, R.drawable.ic_tab_clock, R.string.menu_clock),
TIMERS(TimerFragment::class.java, R.drawable.ic_tab_timer, R.string.menu_timer),
STOPWATCH(StopwatchFragment::class.java,
R.drawable.ic_tab_stopwatch, R.string.menu_stopwatch);
val fragmentClassName: String = fragmentClass.name
}
private var mContext: Context? = null
/** The model from which tab data are fetched. */
private lateinit var mTabModel: TabModel
/** The model from which formatted strings are fetched. */
private lateinit var mFormattedStringModel: FormattedStringModel
/** The model from which timed callbacks originate. */
private lateinit var mPeriodicCallbackModel: PeriodicCallbackModel
/**
* The context may be set precisely once during the application life.
*/
fun init(context: Context, prefs: SharedPreferences) {
if (mContext !== context) {
mContext = context.applicationContext
mPeriodicCallbackModel = PeriodicCallbackModel(mContext!!)
mFormattedStringModel = FormattedStringModel(mContext!!)
mTabModel = TabModel(prefs)
}
}
/**
* To display the alarm clock in this font, use the character [R.string.clock_emoji].
*
* @return a special font containing a glyph that draws an alarm clock
*/
val alarmIconTypeface: Typeface
get() = Typeface.createFromAsset(mContext!!.assets, "fonts/clock.ttf")
//
// Formatted Strings
//
/**
* This method is intended to be used when formatting numbers occurs in a hotspot such as the
* update loop of a timer or stopwatch. It returns cached results when possible in order to
* provide speed and limit garbage to be collected by the virtual machine.
*
* @param value a positive integer to format as a String
* @return the `value` formatted as a String in the current locale
* @throws IllegalArgumentException if `value` is negative
*/
fun getFormattedNumber(value: Int): String {
Utils.enforceMainLooper()
return mFormattedStringModel.getFormattedNumber(value)
}
/**
* This method is intended to be used when formatting numbers occurs in a hotspot such as the
* update loop of a timer or stopwatch. It returns cached results when possible in order to
* provide speed and limit garbage to be collected by the virtual machine.
*
* @param value a positive integer to format as a String
* @param length the length of the String; zeroes are padded to match this length
* @return the `value` formatted as a String in the current locale and padded to the
* requested `length`
* @throws IllegalArgumentException if `value` is negative
*/
fun getFormattedNumber(value: Int, length: Int): String {
Utils.enforceMainLooper()
return mFormattedStringModel.getFormattedNumber(value, length)
}
/**
* This method is intended to be used when formatting numbers occurs in a hotspot such as the
* update loop of a timer or stopwatch. It returns cached results when possible in order to
* provide speed and limit garbage to be collected by the virtual machine.
*
* @param negative force a minus sign (-) onto the display, even if `value` is `0`
* @param value a positive integer to format as a String
* @param length the length of the String; zeroes are padded to match this length. If
* `negative` is `true` the return value will contain a minus sign and a total
* length of `length + 1`.
* @return the `value` formatted as a String in the current locale and padded to the
* requested `length`
* @throws IllegalArgumentException if `value` is negative
*/
fun getFormattedNumber(negative: Boolean, value: Int, length: Int): String {
Utils.enforceMainLooper()
return mFormattedStringModel.getFormattedNumber(negative, value, length)
}
/**
* @param calendarDay any of the following values
*
* * [Calendar.SUNDAY]
* * [Calendar.MONDAY]
* * [Calendar.TUESDAY]
* * [Calendar.WEDNESDAY]
* * [Calendar.THURSDAY]
* * [Calendar.FRIDAY]
* * [Calendar.SATURDAY]
*
* @return single-character version of weekday name; e.g.: 'S', 'M', 'T', 'W', 'T', 'F', 'S'
*/
fun getShortWeekday(calendarDay: Int): String? {
Utils.enforceMainLooper()
return mFormattedStringModel.getShortWeekday(calendarDay)
}
/**
* @param calendarDay any of the following values
*
* * [Calendar.SUNDAY]
* * [Calendar.MONDAY]
* * [Calendar.TUESDAY]
* * [Calendar.WEDNESDAY]
* * [Calendar.THURSDAY]
* * [Calendar.FRIDAY]
* * [Calendar.SATURDAY]
*
* @return full weekday name; e.g.: 'Sunday', 'Monday', 'Tuesday', etc.
*/
fun getLongWeekday(calendarDay: Int): String? {
Utils.enforceMainLooper()
return mFormattedStringModel.getLongWeekday(calendarDay)
}
//
// Animations
//
/**
* @return the duration in milliseconds of short animations
*/
val shortAnimationDuration: Long
get() {
Utils.enforceMainLooper()
return mContext!!.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
}
/**
* @return the duration in milliseconds of long animations
*/
val longAnimationDuration: Long
get() {
Utils.enforceMainLooper()
return mContext!!.resources.getInteger(android.R.integer.config_longAnimTime).toLong()
}
//
// Tabs
//
/**
* @param tabListener to be notified when the selected tab changes
*/
fun addTabListener(tabListener: TabListener) {
Utils.enforceMainLooper()
mTabModel.addTabListener(tabListener)
}
/**
* @param tabListener to no longer be notified when the selected tab changes
*/
fun removeTabListener(tabListener: TabListener) {
Utils.enforceMainLooper()
mTabModel.removeTabListener(tabListener)
}
/**
* @return the number of tabs
*/
val tabCount: Int
get() {
Utils.enforceMainLooper()
return mTabModel.tabCount
}
/**
* @param ordinal the ordinal of the tab
* @return the tab at the given `ordinal`
*/
fun getTab(ordinal: Int): Tab {
Utils.enforceMainLooper()
return mTabModel.getTab(ordinal)
}
/**
* @param position the position of the tab in the user interface
* @return the tab at the given `ordinal`
*/
fun getTabAt(position: Int): Tab {
Utils.enforceMainLooper()
return mTabModel.getTabAt(position)
}
var selectedTab: Tab
/**
* @return an enumerated value indicating the currently selected primary tab
*/
get() {
Utils.enforceMainLooper()
return mTabModel.selectedTab
}
/**
* @param tab an enumerated value indicating the newly selected primary tab
*/
set(tab) {
Utils.enforceMainLooper()
mTabModel.setSelectedTab(tab)
}
/**
* @param tabScrollListener to be notified when the scroll position of the selected tab changes
*/
fun addTabScrollListener(tabScrollListener: TabScrollListener) {
Utils.enforceMainLooper()
mTabModel.addTabScrollListener(tabScrollListener)
}
/**
* @param tabScrollListener to be notified when the scroll position of the selected tab changes
*/
fun removeTabScrollListener(tabScrollListener: TabScrollListener) {
Utils.enforceMainLooper()
mTabModel.removeTabScrollListener(tabScrollListener)
}
/**
* Updates the scrolling state in the [UiDataModel] for this tab.
*
* @param tab an enumerated value indicating the tab reporting its vertical scroll position
* @param scrolledToTop `true` iff the vertical scroll position of the tab is at the top
*/
fun setTabScrolledToTop(tab: Tab, scrolledToTop: Boolean) {
Utils.enforceMainLooper()
mTabModel.setTabScrolledToTop(tab, scrolledToTop)
}
/**
* @return `true` iff the content in the selected tab is currently scrolled to the top
*/
val isSelectedTabScrolledToTop: Boolean
get() {
Utils.enforceMainLooper()
return mTabModel.isTabScrolledToTop(selectedTab)
}
//
// Shortcut Ids
//
/**
* @param category which category of shortcut of which to get the id
* @param action the desired action to perform
* @return the id of the shortcut
*/
fun getShortcutId(@StringRes category: Int, @StringRes action: Int): String {
return if (category == R.string.category_stopwatch) {
mContext!!.getString(category)
} else {
mContext!!.getString(category) + "_" + mContext!!.getString(action)
}
}
//
// Timed Callbacks
//
/**
* @param runnable to be called every minute
* @param offset an offset applied to the minute to control when the callback occurs
*/
fun addMinuteCallback(runnable: Runnable, offset: Long) {
Utils.enforceMainLooper()
mPeriodicCallbackModel.addMinuteCallback(runnable, offset)
}
/**
* @param runnable to be called every quarter-hour
*/
fun addQuarterHourCallback(runnable: Runnable) {
Utils.enforceMainLooper()
mPeriodicCallbackModel.addQuarterHourCallback(runnable)
}
/**
* @param runnable to be called every hour
*/
fun addHourCallback(runnable: Runnable) {
Utils.enforceMainLooper()
mPeriodicCallbackModel.addHourCallback(runnable)
}
/**
* @param runnable to be called every midnight
*/
fun addMidnightCallback(runnable: Runnable) {
Utils.enforceMainLooper()
mPeriodicCallbackModel.addMidnightCallback(runnable)
}
/**
* @param runnable to no longer be called periodically
*/
fun removePeriodicCallback(runnable: Runnable) {
Utils.enforceMainLooper()
mPeriodicCallbackModel.removePeriodicCallback(runnable)
}
companion object {
/** The single instance of this data model that exists for the life of the application. */
val sUiDataModel = UiDataModel()
@get:JvmStatic
val uiDataModel
get() = sUiDataModel
}
}