1 /* 2 * Copyright (C) 2020 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 17 package com.android.deskclock 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.animation.AnimatorSet 22 import android.content.Intent 23 import android.graphics.drawable.Drawable 24 import android.os.Bundle 25 import android.text.format.DateUtils 26 import android.view.KeyEvent 27 import android.view.Menu 28 import android.view.MenuItem 29 import android.view.View 30 import android.widget.Button 31 import android.widget.ImageView 32 import android.widget.TextView 33 import androidx.annotation.StringRes 34 import androidx.appcompat.app.ActionBar 35 import androidx.appcompat.widget.Toolbar 36 import androidx.fragment.app.Fragment 37 import androidx.viewpager.widget.ViewPager 38 import androidx.viewpager.widget.ViewPager.OnPageChangeListener 39 import androidx.viewpager.widget.ViewPager.SCROLL_STATE_DRAGGING 40 import androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE 41 import androidx.viewpager.widget.ViewPager.SCROLL_STATE_SETTLING 42 43 import com.android.deskclock.FabContainer.UpdateFabFlag 44 import com.android.deskclock.LabelDialogFragment.AlarmLabelDialogHandler 45 import com.android.deskclock.actionbarmenu.MenuItemControllerFactory 46 import com.android.deskclock.actionbarmenu.NightModeMenuItemController 47 import com.android.deskclock.actionbarmenu.OptionsMenuManager 48 import com.android.deskclock.actionbarmenu.SettingsMenuItemController 49 import com.android.deskclock.data.DataModel 50 import com.android.deskclock.data.OnSilentSettingsListener 51 import com.android.deskclock.events.Events 52 import com.android.deskclock.provider.Alarm 53 import com.android.deskclock.uidata.TabListener 54 import com.android.deskclock.uidata.UiDataModel 55 import com.android.deskclock.widget.toast.SnackbarManager 56 57 import com.google.android.material.snackbar.Snackbar 58 import com.google.android.material.tabs.TabLayout 59 60 /** 61 * The main activity of the application which displays 4 different tabs contains alarms, world 62 * clocks, timers and a stopwatch. 63 */ 64 class DeskClock : BaseActivity(), FabContainer, AlarmLabelDialogHandler { 65 /** Models the interesting state of display the [.mFab] button may inhabit. */ 66 private enum class FabState { 67 SHOWING, HIDE_ARMED, HIDING 68 } 69 70 /** Coordinates handling of context menu items. */ 71 private val mOptionsMenuManager = OptionsMenuManager() 72 73 /** Shrinks the [.mFab], [.mLeftButton] and [.mRightButton] to nothing. */ 74 private val mHideAnimation = AnimatorSet() 75 76 /** Grows the [.mFab], [.mLeftButton] and [.mRightButton] to natural sizes. */ 77 private val mShowAnimation = AnimatorSet() 78 79 /** Hides, updates, and shows only the [.mFab]; the buttons are untouched. */ 80 private val mUpdateFabOnlyAnimation = AnimatorSet() 81 82 /** Hides, updates, and shows only the [.mLeftButton] and [.mRightButton]. */ 83 private val mUpdateButtonsOnlyAnimation = AnimatorSet() 84 85 /** Automatically starts the [.mShowAnimation] after [.mHideAnimation] ends. */ 86 private val mAutoStartShowListener: AnimatorListenerAdapter = AutoStartShowListener() 87 88 /** Updates the user interface to reflect the selected tab from the backing model. */ 89 private val mTabChangeWatcher: TabListener = TabChangeWatcher() 90 91 /** Shows/hides a snackbar explaining which setting is suppressing alarms from firing. */ 92 private val mSilentSettingChangeWatcher: OnSilentSettingsListener = SilentSettingChangeWatcher() 93 94 /** Displays a snackbar explaining why alarms may not fire or may fire silently. */ 95 private var mShowSilentSettingSnackbarRunnable: Runnable? = null 96 97 /** The view to which snackbar items are anchored. */ 98 private lateinit var mSnackbarAnchor: View 99 100 /** The current display state of the [.mFab]. */ 101 private var mFabState = FabState.SHOWING 102 103 /** The single floating-action button shared across all tabs in the user interface. */ 104 private lateinit var mFab: ImageView 105 106 /** The button left of the [.mFab] shared across all tabs in the user interface. */ 107 private lateinit var mLeftButton: Button 108 109 /** The button right of the [.mFab] shared across all tabs in the user interface. */ 110 private lateinit var mRightButton: Button 111 112 /** The controller that shows the drop shadow when content is not scrolled to the top. */ 113 private var mDropShadowController: DropShadowController? = null 114 115 /** The ViewPager that pages through the fragments representing the content of the tabs. */ 116 private lateinit var mFragmentTabPager: ViewPager 117 118 /** Generates the fragments that are displayed by the [.mFragmentTabPager]. */ 119 private lateinit var mFragmentTabPagerAdapter: FragmentTabPagerAdapter 120 121 /** The container that stores the tab headers. */ 122 private lateinit var mTabLayout: TabLayout 123 124 /** `true` when a settings change necessitates recreating this activity. */ 125 private var mRecreateActivity = false 126 onNewIntentnull127 override fun onNewIntent(newIntent: Intent) { 128 super.onNewIntent(newIntent) 129 130 // Fragments may query the latest intent for information, so update the intent. 131 setIntent(newIntent) 132 } 133 onCreatenull134 protected override fun onCreate(savedInstanceState: Bundle?) { 135 super.onCreate(savedInstanceState) 136 137 setContentView(R.layout.desk_clock) 138 mSnackbarAnchor = findViewById(R.id.content) 139 140 // Configure the toolbar. 141 val toolbar: Toolbar = findViewById(R.id.toolbar) as Toolbar 142 setSupportActionBar(toolbar) 143 144 val actionBar: ActionBar? = getSupportActionBar() 145 actionBar?.setDisplayShowTitleEnabled(false) 146 147 // Configure the menu item controllers add behavior to the toolbar. 148 mOptionsMenuManager.addMenuItemController( 149 NightModeMenuItemController(this), SettingsMenuItemController(this)) 150 mOptionsMenuManager.addMenuItemController( 151 *MenuItemControllerFactory.buildMenuItemControllers(this)) 152 153 // Inflate the menu during creation to avoid a double layout pass. Otherwise, the menu 154 // inflation occurs *after* the initial draw and a second layout pass adds in the menu. 155 onCreateOptionsMenu(toolbar.getMenu()) 156 157 // Create the tabs that make up the user interface. 158 mTabLayout = findViewById(R.id.tabs) as TabLayout 159 val tabCount: Int = UiDataModel.uiDataModel.tabCount 160 val showTabLabel: Boolean = getResources().getBoolean(R.bool.showTabLabel) 161 val showTabHorizontally: Boolean = getResources().getBoolean(R.bool.showTabHorizontally) 162 for (i in 0 until tabCount) { 163 val tabModel: UiDataModel.Tab = UiDataModel.uiDataModel.getTab(i) 164 @StringRes val labelResId: Int = tabModel.labelResId 165 166 val tab: TabLayout.Tab = mTabLayout.newTab() 167 .setTag(tabModel) 168 .setIcon(tabModel.iconResId) 169 .setContentDescription(labelResId) 170 171 if (showTabLabel) { 172 tab.setText(labelResId) 173 tab.setCustomView(R.layout.tab_item) 174 175 val text = tab.getCustomView()!!.findViewById(android.R.id.text1) as TextView 176 text.setTextColor(mTabLayout.getTabTextColors()) 177 178 // Bind the icon to the TextView. 179 val icon: Drawable? = tab.getIcon() 180 if (showTabHorizontally) { 181 // Remove the icon so it doesn't affect the minimum TabLayout height. 182 tab.setIcon(null) 183 text.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) 184 } else { 185 text.setCompoundDrawablesRelativeWithIntrinsicBounds(null, icon, null, null) 186 } 187 } 188 189 mTabLayout.addTab(tab) 190 } 191 192 // Configure the buttons shared by the tabs. 193 mFab = findViewById(R.id.fab) as ImageView 194 mLeftButton = findViewById(R.id.left_button) as Button 195 mRightButton = findViewById(R.id.right_button) as Button 196 197 mFab.setOnClickListener { selectedDeskClockFragment.onFabClick(mFab) } 198 mLeftButton.setOnClickListener { 199 selectedDeskClockFragment.onLeftButtonClick(mLeftButton) 200 } 201 mRightButton.setOnClickListener { 202 selectedDeskClockFragment.onRightButtonClick(mRightButton) 203 } 204 205 val duration: Long = UiDataModel.uiDataModel.shortAnimationDuration 206 207 val hideFabAnimation = AnimatorUtils.getScaleAnimator(mFab, 1f, 0f) 208 val showFabAnimation = AnimatorUtils.getScaleAnimator(mFab, 0f, 1f) 209 210 val leftHideAnimation = AnimatorUtils.getScaleAnimator(mLeftButton, 1f, 0f) 211 val rightHideAnimation = AnimatorUtils.getScaleAnimator(mRightButton, 1f, 0f) 212 val leftShowAnimation = AnimatorUtils.getScaleAnimator(mLeftButton, 0f, 1f) 213 val rightShowAnimation = AnimatorUtils.getScaleAnimator(mRightButton, 0f, 1f) 214 215 hideFabAnimation.addListener(object : AnimatorListenerAdapter() { 216 override fun onAnimationEnd(animation: Animator) { 217 selectedDeskClockFragment.onUpdateFab(mFab) 218 } 219 }) 220 221 leftHideAnimation.addListener(object : AnimatorListenerAdapter() { 222 override fun onAnimationEnd(animation: Animator) { 223 selectedDeskClockFragment.onUpdateFabButtons(mLeftButton, mRightButton) 224 } 225 }) 226 227 // Build the reusable animations that hide and show the fab and left/right buttons. 228 // These may be used independently or be chained together. 229 mHideAnimation 230 .setDuration(duration) 231 .play(hideFabAnimation) 232 .with(leftHideAnimation) 233 .with(rightHideAnimation) 234 235 mShowAnimation 236 .setDuration(duration) 237 .play(showFabAnimation) 238 .with(leftShowAnimation) 239 .with(rightShowAnimation) 240 241 // Build the reusable animation that hides and shows only the fab. 242 mUpdateFabOnlyAnimation 243 .setDuration(duration) 244 .play(showFabAnimation) 245 .after(hideFabAnimation) 246 247 // Build the reusable animation that hides and shows only the buttons. 248 mUpdateButtonsOnlyAnimation 249 .setDuration(duration) 250 .play(leftShowAnimation) 251 .with(rightShowAnimation) 252 .after(leftHideAnimation) 253 .after(rightHideAnimation) 254 255 // Customize the view pager. 256 mFragmentTabPagerAdapter = FragmentTabPagerAdapter(this) 257 mFragmentTabPager = findViewById(R.id.desk_clock_pager) as ViewPager 258 // Keep all four tabs to minimize jank. 259 mFragmentTabPager.setOffscreenPageLimit(3) 260 // Set Accessibility Delegate to null so view pager doesn't intercept movements and 261 // prevent the fab from being selected. 262 mFragmentTabPager.setAccessibilityDelegate(null) 263 // Mirror changes made to the selected page of the view pager into UiDataModel. 264 mFragmentTabPager.addOnPageChangeListener(PageChangeWatcher()) 265 mFragmentTabPager.setAdapter(mFragmentTabPagerAdapter) 266 267 // Mirror changes made to the selected tab into UiDataModel. 268 mTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { 269 override fun onTabSelected(tab: TabLayout.Tab) { 270 UiDataModel.uiDataModel.selectedTab = tab.getTag() as UiDataModel.Tab 271 } 272 273 override fun onTabUnselected(tab: TabLayout.Tab) { 274 } 275 276 override fun onTabReselected(tab: TabLayout.Tab) { 277 } 278 }) 279 280 // Honor changes to the selected tab from outside entities. 281 UiDataModel.uiDataModel.addTabListener(mTabChangeWatcher) 282 } 283 onStartnull284 override fun onStart() { 285 super.onStart() 286 DataModel.dataModel.addSilentSettingsListener(mSilentSettingChangeWatcher) 287 DataModel.dataModel.isApplicationInForeground = true 288 } 289 onResumenull290 override fun onResume() { 291 super.onResume() 292 293 val dropShadow: View = findViewById(R.id.drop_shadow) 294 mDropShadowController = DropShadowController(dropShadow, UiDataModel.uiDataModel, 295 mSnackbarAnchor.findViewById(R.id.tab_hairline)) 296 297 // ViewPager does not save state; this honors the selected tab in the user interface. 298 updateCurrentTab() 299 } 300 onPostResumenull301 override fun onPostResume() { 302 super.onPostResume() 303 304 if (mRecreateActivity) { 305 mRecreateActivity = false 306 307 // A runnable must be posted here or the new DeskClock activity will be recreated in a 308 // paused state, even though it is the foreground activity. 309 mFragmentTabPager.post(Runnable { recreate() }) 310 } 311 } 312 onPausenull313 override fun onPause() { 314 if (mDropShadowController != null) { 315 mDropShadowController!!.stop() 316 mDropShadowController = null 317 } 318 319 super.onPause() 320 } 321 onStopnull322 override fun onStop() { 323 DataModel.dataModel.removeSilentSettingsListener(mSilentSettingChangeWatcher) 324 if (!isChangingConfigurations()) { 325 DataModel.dataModel.isApplicationInForeground = false 326 } 327 328 super.onStop() 329 } 330 onDestroynull331 override fun onDestroy() { 332 UiDataModel.uiDataModel.removeTabListener(mTabChangeWatcher) 333 super.onDestroy() 334 } 335 onCreateOptionsMenunull336 override fun onCreateOptionsMenu(menu: Menu): Boolean { 337 mOptionsMenuManager.onCreateOptionsMenu(menu) 338 return true 339 } 340 onPrepareOptionsMenunull341 override fun onPrepareOptionsMenu(menu: Menu): Boolean { 342 super.onPrepareOptionsMenu(menu) 343 mOptionsMenuManager.onPrepareOptionsMenu(menu) 344 return true 345 } 346 onOptionsItemSelectednull347 override fun onOptionsItemSelected(item: MenuItem): Boolean { 348 return mOptionsMenuManager.onOptionsItemSelected(item) || super.onOptionsItemSelected(item) 349 } 350 351 /** 352 * Called by the LabelDialogFormat class after the dialog is finished. 353 */ onDialogLabelSetnull354 override fun onDialogLabelSet(alarm: Alarm, label: String, tag: String) { 355 val frag: Fragment? = supportFragmentManager.findFragmentByTag(tag) 356 if (frag is AlarmClockFragment) { 357 frag.setLabel(alarm, label) 358 } 359 } 360 361 /** 362 * Listens for keyboard activity for the tab fragments to handle if necessary. A tab may want to 363 * respond to key presses even if they are not currently focused. 364 */ onKeyDownnull365 override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { 366 return (selectedDeskClockFragment.onKeyDown(keyCode, event) || 367 super.onKeyDown(keyCode, event)) 368 } 369 updateFabnull370 override fun updateFab(@UpdateFabFlag updateType: Int) { 371 val f = selectedDeskClockFragment 372 373 when (updateType and FabContainer.FAB_ANIMATION_MASK) { 374 FabContainer.FAB_SHRINK_AND_EXPAND -> mUpdateFabOnlyAnimation.start() 375 FabContainer.FAB_IMMEDIATE -> f.onUpdateFab(mFab) 376 FabContainer.FAB_MORPH -> f.onMorphFab(mFab) 377 } 378 when (updateType and FabContainer.FAB_REQUEST_FOCUS_MASK) { 379 FabContainer.FAB_REQUEST_FOCUS -> mFab.requestFocus() 380 } 381 when (updateType and FabContainer.BUTTONS_ANIMATION_MASK) { 382 FabContainer.BUTTONS_IMMEDIATE -> f.onUpdateFabButtons(mLeftButton, mRightButton) 383 FabContainer.BUTTONS_SHRINK_AND_EXPAND -> mUpdateButtonsOnlyAnimation.start() 384 } 385 when (updateType and FabContainer.BUTTONS_DISABLE_MASK) { 386 FabContainer.BUTTONS_DISABLE -> { 387 mLeftButton.isClickable = false 388 mRightButton.isClickable = false 389 } 390 } 391 when (updateType and FabContainer.FAB_AND_BUTTONS_SHRINK_EXPAND_MASK) { 392 FabContainer.FAB_AND_BUTTONS_SHRINK -> mHideAnimation.start() 393 FabContainer.FAB_AND_BUTTONS_EXPAND -> mShowAnimation.start() 394 } 395 } 396 onActivityResultnull397 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 398 // Recreate the activity if any settings have been changed 399 if (requestCode == SettingsMenuItemController.REQUEST_CHANGE_SETTINGS && 400 resultCode == RESULT_OK) { 401 mRecreateActivity = true 402 } 403 } 404 405 /** 406 * Configure the [.mFragmentTabPager] and [.mTabLayout] to display UiDataModel's 407 * selected tab. 408 */ updateCurrentTabnull409 private fun updateCurrentTab() { 410 // Fetch the selected tab from the source of truth: UiDataModel. 411 val selectedTab: UiDataModel.Tab = UiDataModel.uiDataModel.selectedTab 412 413 // Update the selected tab in the tablayout if it does not agree with UiDataModel. 414 for (i in 0 until mTabLayout.getTabCount()) { 415 val tab: TabLayout.Tab? = mTabLayout.getTabAt(i) 416 if (tab?.getTag() == selectedTab && !tab.isSelected()) { 417 tab.select() 418 break 419 } 420 } 421 422 // Update the selected fragment in the viewpager if it does not agree with UiDataModel. 423 for (i in 0 until mFragmentTabPagerAdapter.count) { 424 val fragment = mFragmentTabPagerAdapter.getDeskClockFragment(i) 425 if (fragment.isTabSelected && mFragmentTabPager.getCurrentItem() != i) { 426 mFragmentTabPager.setCurrentItem(i) 427 break 428 } 429 } 430 } 431 432 /** 433 * @return the DeskClockFragment that is currently selected according to UiDataModel 434 */ 435 private val selectedDeskClockFragment: DeskClockFragment 436 get() { 437 for (i in 0 until mFragmentTabPagerAdapter.count) { 438 val fragment = mFragmentTabPagerAdapter.getDeskClockFragment(i) 439 if (fragment.isTabSelected) { 440 return fragment 441 } 442 } 443 val selectedTab: UiDataModel.Tab = UiDataModel.uiDataModel.selectedTab 444 throw IllegalStateException("Unable to locate selected fragment ($selectedTab)") 445 } 446 447 /** 448 * @return a Snackbar that displays the message with the given id for 5 seconds 449 */ createSnackbarnull450 private fun createSnackbar(@StringRes messageId: Int): Snackbar { 451 return Snackbar.make(mSnackbarAnchor, messageId, 5000) 452 } 453 454 /** 455 * As the view pager changes the selected page, update the model to record the new selected tab. 456 */ 457 private inner class PageChangeWatcher : OnPageChangeListener { 458 /** The last reported page scroll state; used to detect exotic state changes. */ 459 private var mPriorState: Int = SCROLL_STATE_IDLE 460 onPageScrollednull461 override fun onPageScrolled( 462 position: Int, 463 positionOffset: Float, 464 positionOffsetPixels: Int 465 ) { 466 // Only hide the fab when a non-zero drag distance is detected. This prevents 467 // over-scrolling from needlessly hiding the fab. 468 if (mFabState == FabState.HIDE_ARMED && positionOffsetPixels != 0) { 469 mFabState = FabState.HIDING 470 mHideAnimation.start() 471 } 472 } 473 onPageScrollStateChangednull474 override fun onPageScrollStateChanged(state: Int) { 475 if (mPriorState == SCROLL_STATE_IDLE && state == SCROLL_STATE_SETTLING) { 476 // The user has tapped a tab button; play the hide and show animations linearly. 477 mHideAnimation.addListener(mAutoStartShowListener) 478 mHideAnimation.start() 479 mFabState = FabState.HIDING 480 } else if (mPriorState == SCROLL_STATE_SETTLING && state == SCROLL_STATE_DRAGGING) { 481 // The user has interrupted settling on a tab and the fab button must be re-hidden. 482 if (mShowAnimation.isStarted) { 483 mShowAnimation.cancel() 484 } 485 if (mHideAnimation.isStarted) { 486 // Let the hide animation finish naturally; don't auto show when it ends. 487 mHideAnimation.removeListener(mAutoStartShowListener) 488 } else { 489 // Start and immediately end the hide animation to jump to the hidden state. 490 mHideAnimation.start() 491 mHideAnimation.end() 492 } 493 mFabState = FabState.HIDING 494 } else if (state != SCROLL_STATE_DRAGGING && mFabState == FabState.HIDING) { 495 // The user has lifted their finger; show the buttons now or after hide ends. 496 if (mHideAnimation.isStarted) { 497 // Finish the hide animation and then start the show animation. 498 mHideAnimation.addListener(mAutoStartShowListener) 499 } else { 500 updateFab(FabContainer.FAB_AND_BUTTONS_IMMEDIATE) 501 mShowAnimation.start() 502 503 // The animation to show the fab has begun; update the state to showing. 504 mFabState = FabState.SHOWING 505 } 506 } else if (state == SCROLL_STATE_DRAGGING) { 507 // The user has started a drag so arm the hide animation. 508 mFabState = FabState.HIDE_ARMED 509 } 510 511 // Update the last known state. 512 mPriorState = state 513 } 514 onPageSelectednull515 override fun onPageSelected(position: Int) { 516 mFragmentTabPagerAdapter.getDeskClockFragment(position).selectTab() 517 } 518 } 519 520 /** 521 * If this listener is attached to [.mHideAnimation] when it ends, the corresponding 522 * [.mShowAnimation] is automatically started. 523 */ 524 private inner class AutoStartShowListener : AnimatorListenerAdapter() { onAnimationEndnull525 override fun onAnimationEnd(animation: Animator) { 526 // Prepare the hide animation for its next use; by default do not auto-show after hide. 527 mHideAnimation.removeListener(mAutoStartShowListener) 528 529 // Update the buttons now that they are no longer visible. 530 updateFab(FabContainer.FAB_AND_BUTTONS_IMMEDIATE) 531 532 // Automatically start the grow animation now that shrinking is complete. 533 mShowAnimation.start() 534 535 // The animation to show the fab has begun; update the state to showing. 536 mFabState = FabState.SHOWING 537 } 538 } 539 540 /** 541 * Shows/hides a snackbar as silencing settings are enabled/disabled. 542 */ 543 private inner class SilentSettingChangeWatcher : OnSilentSettingsListener { onSilentSettingsChangenull544 override fun onSilentSettingsChange( 545 before: DataModel.SilentSetting?, 546 after: DataModel.SilentSetting? 547 ) { 548 if (mShowSilentSettingSnackbarRunnable != null) { 549 mSnackbarAnchor.removeCallbacks(mShowSilentSettingSnackbarRunnable) 550 mShowSilentSettingSnackbarRunnable = null 551 } 552 553 if (after == null) { 554 SnackbarManager.dismiss() 555 } else { 556 mShowSilentSettingSnackbarRunnable = ShowSilentSettingSnackbarRunnable(after) 557 mSnackbarAnchor.postDelayed(mShowSilentSettingSnackbarRunnable, 558 DateUtils.SECOND_IN_MILLIS) 559 } 560 } 561 } 562 563 /** 564 * Displays a snackbar that indicates a system setting is currently silencing alarms. 565 */ 566 private inner class ShowSilentSettingSnackbarRunnable( 567 private val mSilentSetting: DataModel.SilentSetting 568 ) : Runnable { runnull569 override fun run() { 570 // Create a snackbar with a message explaining the setting that is silencing alarms. 571 val snackbar: Snackbar = createSnackbar(mSilentSetting.labelResId) 572 573 // Set the associated corrective action if one exists. 574 if (mSilentSetting.isActionEnabled(this@DeskClock)) { 575 val actionResId: Int = mSilentSetting.actionResId 576 snackbar.setAction(actionResId, mSilentSetting.actionListener) 577 } 578 579 SnackbarManager.show(snackbar) 580 } 581 } 582 583 /** 584 * As the model reports changes to the selected tab, update the user interface. 585 */ 586 private inner class TabChangeWatcher : TabListener { selectedTabChangednull587 override fun selectedTabChanged( 588 oldSelectedTab: UiDataModel.Tab, 589 newSelectedTab: UiDataModel.Tab 590 ) { 591 // Update the view pager and tab layout to agree with the model. 592 updateCurrentTab() 593 594 // Avoid sending events for the initial tab selection on launch and re-selecting a tab 595 // after a configuration change. 596 if (DataModel.dataModel.isApplicationInForeground) { 597 when (newSelectedTab) { 598 UiDataModel.Tab.ALARMS -> { 599 Events.sendAlarmEvent(R.string.action_show, R.string.label_deskclock) 600 } 601 UiDataModel.Tab.CLOCKS -> { 602 Events.sendClockEvent(R.string.action_show, R.string.label_deskclock) 603 } 604 UiDataModel.Tab.TIMERS -> { 605 Events.sendTimerEvent(R.string.action_show, R.string.label_deskclock) 606 } 607 UiDataModel.Tab.STOPWATCH -> { 608 Events.sendStopwatchEvent(R.string.action_show, R.string.label_deskclock) 609 } 610 } 611 } 612 613 // If the hide animation has already completed, the buttons must be updated now when the 614 // new tab is known. Otherwise they are updated at the end of the hide animation. 615 if (!mHideAnimation.isStarted) { 616 updateFab(FabContainer.FAB_AND_BUTTONS_IMMEDIATE) 617 } 618 } 619 } 620 }