/* * 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 } }