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.content.Context
20 import android.graphics.Canvas
21 import android.graphics.Color
22 import android.graphics.Paint
23 import android.graphics.RectF
24 import android.util.AttributeSet
25 import android.view.View
26 
27 import com.android.deskclock.R
28 import com.android.deskclock.ThemeUtils
29 import com.android.deskclock.Utils
30 import com.android.deskclock.data.Timer
31 
32 import kotlin.math.cos
33 import kotlin.math.min
34 import kotlin.math.sin
35 
36 /**
37  * Custom view that draws timer progress as a circle.
38  */
39 class TimerCircleView @JvmOverloads constructor(
40     context: Context,
41     attrs: AttributeSet? = null
42 ) : View(context, attrs) {
43     /** The size of the dot indicating the progress through the timer.  */
44     private val mDotRadius: Float
45 
46     /** An amount to subtract from the true radius to account for drawing thicknesses.  */
47     private val mRadiusOffset: Float
48 
49     /** The color indicating the remaining portion of the timer.  */
50     private val mRemainderColor: Int
51 
52     /** The color indicating the completed portion of the timer.  */
53     private val mCompletedColor: Int
54 
55     /** The size of the stroke that paints the timer circle.  */
56     private val mStrokeSize: Float
57 
58     private val mPaint = Paint()
59     private val mFill = Paint()
60     private val mArcRect = RectF()
61 
62     private var mTimer: Timer? = null
63 
64     init {
65         val resources = context.resources
66         val dotDiameter = resources.getDimension(R.dimen.circletimer_dot_size)
67 
68         mDotRadius = dotDiameter / 2f
69         mStrokeSize = resources.getDimension(R.dimen.circletimer_circle_size)
70         mRadiusOffset = Utils.calculateRadiusOffset(mStrokeSize, dotDiameter, 0f)
71 
72         mRemainderColor = Color.WHITE
73         mCompletedColor = ThemeUtils.resolveColor(context, R.attr.colorAccent)
74 
75         mPaint.isAntiAlias = true
76         mPaint.style = Paint.Style.STROKE
77 
78         mFill.isAntiAlias = true
79         mFill.color = mCompletedColor
80         mFill.style = Paint.Style.FILL
81     }
82 
updatenull83     fun update(timer: Timer) {
84         if (mTimer !== timer) {
85             mTimer = timer
86             postInvalidateOnAnimation()
87         }
88     }
89 
onDrawnull90     public override fun onDraw(canvas: Canvas) {
91         if (mTimer == null) {
92             return
93         }
94 
95         // Compute the size and location of the circle to be drawn.
96         val xCenter = width / 2
97         val yCenter = height / 2
98         val radius = min(xCenter, yCenter) - mRadiusOffset
99 
100         // Reset old painting state.
101         mPaint.color = mRemainderColor
102         mPaint.strokeWidth = mStrokeSize
103 
104         // If the timer is reset, draw a simple white circle.
105         val redPercent: Float
106         when {
107             mTimer!!.isReset -> {
108                 // Draw a complete white circle; no red arc required.
109                 canvas.drawCircle(xCenter.toFloat(), yCenter.toFloat(), radius, mPaint)
110 
111                 // Red percent is 0 since no timer progress has been made.
112                 redPercent = 0f
113             }
114             mTimer!!.isExpired -> {
115                 mPaint.color = mCompletedColor
116 
117                 // Draw a complete white circle; no red arc required.
118                 canvas.drawCircle(xCenter.toFloat(), yCenter.toFloat(), radius, mPaint)
119 
120                 // Red percent is 1 since the timer has expired.
121                 redPercent = 1f
122             }
123             else -> {
124                 // Draw a combination of red and white arcs to create a circle.
125                 mArcRect.top = yCenter - radius
126                 mArcRect.bottom = yCenter + radius
127                 mArcRect.left = xCenter - radius
128                 mArcRect.right = xCenter + radius
129                 redPercent = min(1f,
130                         mTimer!!.elapsedTime.toFloat() / mTimer!!.totalLength.toFloat())
131                 val whitePercent = 1 - redPercent
132 
133                 // Draw a white arc to indicate the amount of timer that remains.
134                 canvas.drawArc(mArcRect, 270f, whitePercent * 360, false, mPaint)
135 
136                 // Draw a red arc to indicate the amount of timer completed.
137                 mPaint.color = mCompletedColor
138                 canvas.drawArc(mArcRect, 270f, -redPercent * 360, false, mPaint)
139             }
140         }
141 
142         // Draw a red dot to indicate current progress through the timer.
143         val dotAngleDegrees = 270 - redPercent * 360
144         val dotAngleRadians = Math.toRadians(dotAngleDegrees.toDouble())
145         val dotX = xCenter + (radius * cos(dotAngleRadians)).toFloat()
146         val dotY = yCenter + (radius * sin(dotAngleRadians)).toFloat()
147         canvas.drawCircle(dotX, dotY, mDotRadius, mFill)
148 
149         if (mTimer!!.isRunning) {
150             postInvalidateOnAnimation()
151         }
152     }
153 }