1 /*
2  * Copyright (C) 2022 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.permissioncontroller.safetycenter.ui
18 
19 import android.transition.AutoTransition
20 import android.transition.Transition
21 import android.transition.TransitionListenerAdapter
22 import android.transition.TransitionManager
23 import android.transition.TransitionSet
24 import android.util.Log
25 import android.view.View
26 import android.view.ViewGroup
27 import android.view.animation.LinearInterpolator
28 import android.widget.TextView
29 import java.time.Duration
30 
31 /**
32  * An animator which can animate a fade in/fade out of either one textView, or several textViews
33  * that are in the same ViewGroup.
34  */
35 class TextFadeAnimator
36 @JvmOverloads
37 constructor(targetIds: List<Int>, changeDuration: Duration = DEFAULT_TEXT_CHANGE_DURATION) {
38 
39     @JvmOverloads
40     constructor(
41         targetId: Int,
42         changeDuration: Duration = DEFAULT_TEXT_CHANGE_DURATION
43     ) : this(listOf(targetId), changeDuration)
44 
45     private val textChangeTransition: TransitionSet
46     init {
47         var transition =
48             AutoTransition()
49                 .setInterpolator(linearInterpolator)
50                 .setDuration(changeDuration.toMillis())
51         for (targetId in targetIds) {
52             transition = transition.addTarget(targetId)
53         }
54         textChangeTransition = transition
55     }
56 
57     @JvmOverloads
animateChangeTextnull58     fun animateChangeText(textView: TextView, text: String, onFinish: Runnable? = null) {
59         animateChangeText(listOf(textView to text), onFinish)
60     }
61 
62     /** Animate changes for a set of textViews under the same parent. */
63     @JvmOverloads
animateChangeTextnull64     fun animateChangeText(textChanges: List<Pair<TextView, String>>, onFinish: Runnable? = null) {
65         if (textChanges.isEmpty()) {
66             return
67         }
68 
69         Log.v(TAG, "Starting text animation")
70 
71         val firstView = textChanges[0].first
72         val parentViewGroup: ViewGroup = firstView.parent as ViewGroup
73         val fadeOutTransition =
74             textChangeTransition
75                 .clone()
76                 .addListener(
77                     object : TransitionListenerAdapter() {
78                         override fun onTransitionEnd(transition: Transition?) {
79                             fadeTextIn(textChanges, parentViewGroup, onFinish)
80                         }
81                     }
82                 )
83         parentViewGroup.post {
84             TransitionManager.beginDelayedTransition(parentViewGroup, fadeOutTransition)
85             Log.v(TAG, "Starting text fade-out transition")
86             for ((textView, _) in textChanges) {
87                 textView.visibility = View.INVISIBLE
88             }
89         }
90     }
91 
fadeTextInnull92     private fun fadeTextIn(
93         textChanges: List<Pair<TextView, String>>,
94         parent: ViewGroup,
95         onFinish: Runnable?
96     ) {
97         val fadeInTransition =
98             textChangeTransition
99                 .clone()
100                 .addListener(
101                     object : TransitionListenerAdapter() {
102                         override fun onTransitionEnd(transition: Transition?) {
103                             Log.v(TAG, String.format("Finishing text animation"))
104                             onFinish?.run()
105                         }
106                     }
107                 )
108 
109         parent.post {
110             TransitionManager.beginDelayedTransition(parent, fadeInTransition)
111             Log.v(TAG, "Starting text fade-in transition")
112             for ((textView, text) in textChanges) {
113                 textView.text = text
114                 textView.visibility = View.VISIBLE
115             }
116         }
117     }
118 
cancelTextChangeAnimationnull119     fun cancelTextChangeAnimation(textView: TextView) {
120         TransitionManager.endTransitions(textView.parent as ViewGroup)
121     }
122 
123     companion object {
124         private const val TAG = "TextFadeAnimator"
125         // Duration is for fade-out & fade-in individually, not combined
126         private val DEFAULT_TEXT_CHANGE_DURATION = Duration.ofMillis(167)
127         private val linearInterpolator = LinearInterpolator()
128     }
129 }
130