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.timer
18 
19 import android.R.attr
20 import android.content.Context
21 import android.content.res.ColorStateList
22 import android.os.SystemClock
23 import android.text.TextUtils
24 import android.util.AttributeSet
25 import android.view.View
26 import android.widget.Button
27 import android.widget.LinearLayout
28 import android.widget.TextView
29 import androidx.core.view.ViewCompat
30 
31 import com.android.deskclock.R
32 import com.android.deskclock.ThemeUtils
33 import com.android.deskclock.TimerTextController
34 import com.android.deskclock.Utils.ClickAccessibilityDelegate
35 import com.android.deskclock.data.Timer
36 
37 /**
38  * This view is a visual representation of a [Timer].
39  */
40 class TimerItem @JvmOverloads constructor(
41     context: Context?,
42     attrs: AttributeSet? = null
43 ) : LinearLayout(context, attrs) {
44     /** Displays the remaining time or time since expiration.  */
45     private lateinit var mTimerText: TextView
46 
47     /** Formats and displays the text in the timer.  */
48     private lateinit var mTimerTextController: TimerTextController
49 
50     /** Displays timer progress as a color circle that changes from white to red.  */
51     private var mCircleView: TimerCircleView? = null
52 
53     /** A button that either resets the timer or adds time to it, depending on its state.  */
54     private lateinit var mResetAddButton: Button
55 
56     /** Displays the label associated with the timer. Tapping it presents an edit dialog.  */
57     private lateinit var mLabelView: TextView
58 
59     /** The last state of the timer that was rendered; used to avoid expensive operations.  */
60     private var mLastState: Timer.State? = null
61 
onFinishInflatenull62     override fun onFinishInflate() {
63         super.onFinishInflate()
64         mLabelView = findViewById<View>(R.id.timer_label) as TextView
65         mResetAddButton = findViewById<View>(R.id.reset_add) as Button
66         mCircleView = findViewById<View>(R.id.timer_time) as TimerCircleView
67         mTimerText = findViewById<View>(R.id.timer_time_text) as TextView
68         mTimerTextController = TimerTextController(mTimerText)
69 
70         val c = mTimerText.context
71         val colorAccent = ThemeUtils.resolveColor(c, R.attr.colorAccent)
72         val textColorPrimary = ThemeUtils.resolveColor(c, attr.textColorPrimary)
73         mTimerText.setTextColor(ColorStateList(
74                 arrayOf(intArrayOf(-attr.state_activated, -attr.state_pressed),
75                 intArrayOf()),
76                 intArrayOf(textColorPrimary, colorAccent)))
77     }
78 
79     /**
80      * Updates this view to display the latest state of the `timer`.
81      */
updatenull82     fun update(timer: Timer) {
83         // Update the time.
84         mTimerTextController.setTimeString(timer.remainingTime)
85 
86         // Update the label if it changed.
87         val label: String? = timer.label
88         if (!TextUtils.equals(label, mLabelView.text)) {
89             mLabelView.text = label
90         }
91 
92         // Update visibility of things that may blink.
93         val blinkOff = SystemClock.elapsedRealtime() % 1000 < 500
94         if (mCircleView != null) {
95             val hideCircle = (timer.isExpired || timer.isMissed) && blinkOff
96             mCircleView!!.visibility = if (hideCircle) View.INVISIBLE else View.VISIBLE
97 
98             if (!hideCircle) {
99                 // Update the progress of the circle.
100                 mCircleView!!.update(timer)
101             }
102         }
103         if (!timer.isPaused || !blinkOff || mTimerText.isPressed) {
104             mTimerText.alpha = 1f
105         } else {
106             mTimerText.alpha = 0f
107         }
108 
109         // Update some potentially expensive areas of the user interface only on state changes.
110         if (timer.state != mLastState) {
111             mLastState = timer.state
112             val context = context
113             when (mLastState) {
114                 Timer.State.RESET, Timer.State.PAUSED -> {
115                     mResetAddButton.setText(R.string.timer_reset)
116                     mResetAddButton.contentDescription = null
117                     mTimerText.isClickable = true
118                     mTimerText.isActivated = false
119                     mTimerText.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
120                     ViewCompat.setAccessibilityDelegate(mTimerText, ClickAccessibilityDelegate(
121                             context.getString(R.string.timer_start), true))
122                 }
123                 Timer.State.RUNNING -> {
124                     val addTimeDesc = context.getString(R.string.timer_plus_one)
125                     mResetAddButton.setText(R.string.timer_add_minute)
126                     mResetAddButton.contentDescription = addTimeDesc
127                     mTimerText.isClickable = true
128                     mTimerText.isActivated = false
129                     mTimerText.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
130                     ViewCompat.setAccessibilityDelegate(mTimerText, ClickAccessibilityDelegate(
131                             context.getString(R.string.timer_pause)))
132                 }
133                 Timer.State.EXPIRED, Timer.State.MISSED -> {
134                     val addTimeDesc = context.getString(R.string.timer_plus_one)
135                     mResetAddButton.setText(R.string.timer_add_minute)
136                     mResetAddButton.contentDescription = addTimeDesc
137                     mTimerText.isClickable = false
138                     mTimerText.isActivated = true
139                     mTimerText.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
140                 }
141                 null -> { }
142             }
143         }
144     }
145 }