• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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 package com.android.wm.shell.windowdecor
17 
18 import android.animation.AnimatorSet
19 import android.animation.ObjectAnimator
20 import android.animation.ValueAnimator
21 import android.annotation.DrawableRes
22 import android.content.Context
23 import android.content.res.ColorStateList
24 import android.graphics.Color
25 import android.graphics.drawable.Drawable
26 import android.util.AttributeSet
27 import android.view.LayoutInflater
28 import android.view.View
29 import android.view.ViewStub
30 import android.widget.FrameLayout
31 import android.widget.ImageButton
32 import android.widget.ProgressBar
33 import android.window.DesktopModeFlags
34 import androidx.core.animation.doOnEnd
35 import androidx.core.animation.doOnStart
36 import androidx.core.content.ContextCompat
37 import com.android.wm.shell.R
38 
39 private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350
40 private const val MAX_DRAWABLE_ALPHA = 255
41 
42 class MaximizeButtonView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) {
43     lateinit var onHoverAnimationFinishedListener: () -> Unit
44     private val hoverProgressAnimatorSet = AnimatorSet()
45     var hoverDisabled = false
46 
47     private lateinit var stubProgressBarContainer: ViewStub
48     private val maximizeWindow: ImageButton
49     private val progressBar: ProgressBar by lazy {
50         (stubProgressBarContainer.inflate() as FrameLayout)
51             .requireViewById(R.id.progress_bar)
52     }
53 
54     init {
55         LayoutInflater.from(context).inflate(R.layout.maximize_menu_button, this, true)
56 
57         stubProgressBarContainer = requireViewById(R.id.stub_progress_bar_container)
58         maximizeWindow = requireViewById(R.id.maximize_window)
59     }
60 
61     fun startHoverAnimation() {
62         if (hoverDisabled) return
63         if (hoverProgressAnimatorSet.isRunning) {
64             cancelHoverAnimation()
65         }
66 
67         maximizeWindow.background.alpha = 0
68 
69         hoverProgressAnimatorSet.playSequentially(
70                 ValueAnimator.ofInt(0, MAX_DRAWABLE_ALPHA)
71                         .setDuration(50)
72                         .apply {
73                             addUpdateListener {
74                                 maximizeWindow.background.alpha = animatedValue as Int
75                             }
76                         },
77                 ObjectAnimator.ofInt(progressBar, "progress", 100)
78                         .setDuration(OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS.toLong())
79                         .apply {
80                             doOnStart {
81                                 progressBar.setProgress(0, false)
82                                 progressBar.visibility = View.VISIBLE
83                             }
84                             doOnEnd {
85                                 progressBar.visibility = View.INVISIBLE
86                                 onHoverAnimationFinishedListener()
87                             }
88                         }
89         )
90         hoverProgressAnimatorSet.start()
91     }
92 
93     fun cancelHoverAnimation() {
94         hoverProgressAnimatorSet.childAnimations.forEach { it.removeAllListeners() }
95         hoverProgressAnimatorSet.cancel()
96         progressBar.visibility = View.INVISIBLE
97     }
98 
99     /**
100      * Set the color tints of the maximize button views.
101      *
102      * @param darkMode whether the app's theme is in dark mode.
103      * @param iconForegroundColor the color tint to use for the maximize icon to match the rest of
104      *   the App Header icons
105      * @param baseForegroundColor the base foreground color tint used by the App Header, used to style
106      *   views within this button using the same base color but with different opacities.
107      */
108     fun setAnimationTints(
109         darkMode: Boolean,
110         iconForegroundColor: ColorStateList? = null,
111         baseForegroundColor: Int? = null,
112         backgroundDrawable: Drawable? = null
113     ) {
114         if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) {
115             requireNotNull(iconForegroundColor) { "Icon foreground color must be non-null" }
116             requireNotNull(baseForegroundColor) { "Base foreground color must be non-null" }
117             requireNotNull(backgroundDrawable) { "Background drawable must be non-null" }
118             maximizeWindow.imageTintList = iconForegroundColor
119             maximizeWindow.background = backgroundDrawable
120             stubProgressBarContainer.setOnInflateListener { _, inflated ->
121                 val progressBar = (inflated as FrameLayout)
122                     .requireViewById(R.id.progress_bar) as ProgressBar
123                 progressBar.progressTintList = ColorStateList.valueOf(baseForegroundColor)
124                     .withAlpha(OPACITY_15)
125                 progressBar.progressBackgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT)
126             }
127         } else {
128             val progressTint = if (darkMode) {
129                 ColorStateList.valueOf(
130                     resources.getColor(R.color.desktop_mode_maximize_menu_progress_dark))
131             } else {
132                 ColorStateList.valueOf(
133                     resources.getColor(R.color.desktop_mode_maximize_menu_progress_light))
134             }
135             val backgroundTint = if (darkMode) {
136                 ContextCompat.getColorStateList(context,
137                     R.color.desktop_mode_caption_button_color_selector_dark)
138             } else {
139                 ContextCompat.getColorStateList(context,
140                     R.color.desktop_mode_caption_button_color_selector_light)
141             }
142             stubProgressBarContainer.setOnInflateListener { _, inflated ->
143                 val progressBar = (inflated as FrameLayout)
144                     .requireViewById(R.id.progress_bar) as ProgressBar
145                 progressBar.progressTintList = progressTint
146             }
147             maximizeWindow.background?.setTintList(backgroundTint)
148         }
149     }
150 
151     /** Set the drawable resource to use for the maximize button. */
152     fun setIcon(@DrawableRes icon: Int) {
153         maximizeWindow.setImageResource(icon)
154     }
155 
156     companion object {
157         private const val OPACITY_15 = 38
158     }
159 }
160