1 /*
<lambda>null2 * Copyright 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 * https://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 @file:Suppress("DEPRECATION")
18
19 package com.google.accompanist.systemuicontroller
20
21 import android.app.Activity
22 import android.content.Context
23 import android.content.ContextWrapper
24 import android.os.Build
25 import android.view.View
26 import android.view.Window
27 import androidx.compose.runtime.Composable
28 import androidx.compose.runtime.Stable
29 import androidx.compose.runtime.remember
30 import androidx.compose.ui.graphics.Color
31 import androidx.compose.ui.graphics.compositeOver
32 import androidx.compose.ui.graphics.luminance
33 import androidx.compose.ui.graphics.toArgb
34 import androidx.compose.ui.platform.LocalView
35 import androidx.compose.ui.window.DialogWindowProvider
36 import androidx.core.view.ViewCompat
37 import androidx.core.view.WindowCompat
38 import androidx.core.view.WindowInsetsCompat
39 import androidx.core.view.WindowInsetsControllerCompat
40
41 /**
42 * A class which provides easy-to-use utilities for updating the System UI bar
43 * colors within Jetpack Compose.
44 *
45 * @sample com.google.accompanist.sample.systemuicontroller.SystemUiControllerSample
46 */
47 @Deprecated(
48 """
49 accompanist/systemuicontroller is deprecated and the API is no longer maintained.
50 We recommend going edge to edge using Activity.enableEdgeToEdge in androidx.activity.
51 For more information please visit https://google.github.io/accompanist/systemuicontroller
52 """
53 )
54 @Stable
55 public interface SystemUiController {
56
57 /**
58 * Control for the behavior of the system bars. This value should be one of the
59 * [WindowInsetsControllerCompat] behavior constants:
60 * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH] (Deprecated),
61 * [WindowInsetsControllerCompat.BEHAVIOR_DEFAULT] and
62 * [WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE].
63 */
64 public var systemBarsBehavior: Int
65
66 /**
67 * Property which holds the status bar visibility. If set to true, show the status bar,
68 * otherwise hide the status bar.
69 */
70 public var isStatusBarVisible: Boolean
71
72 /**
73 * Property which holds the navigation bar visibility. If set to true, show the navigation bar,
74 * otherwise hide the navigation bar.
75 */
76 public var isNavigationBarVisible: Boolean
77
78 /**
79 * Property which holds the status & navigation bar visibility. If set to true, show both bars,
80 * otherwise hide both bars.
81 */
82 public var isSystemBarsVisible: Boolean
83 get() = isNavigationBarVisible && isStatusBarVisible
84 set(value) {
85 isStatusBarVisible = value
86 isNavigationBarVisible = value
87 }
88
89 /**
90 * Set the status bar color.
91 *
92 * @param color The **desired** [Color] to set. This may require modification if running on an
93 * API level that only supports white status bar icons.
94 * @param darkIcons Whether dark status bar icons would be preferable.
95 * @param transformColorForLightContent A lambda which will be invoked to transform [color] if
96 * dark icons were requested but are not available. Defaults to applying a black scrim.
97 *
98 * @see statusBarDarkContentEnabled
99 */
100 public fun setStatusBarColor(
101 color: Color,
102 darkIcons: Boolean = color.luminance() > 0.5f,
103 transformColorForLightContent: (Color) -> Color = BlackScrimmed
104 )
105
106 /**
107 * Set the navigation bar color.
108 *
109 * @param color The **desired** [Color] to set. This may require modification if running on an
110 * API level that only supports white navigation bar icons. Additionally this will be ignored
111 * and [Color.Transparent] will be used on API 29+ where gesture navigation is preferred or the
112 * system UI automatically applies background protection in other navigation modes.
113 * @param darkIcons Whether dark navigation bar icons would be preferable.
114 * @param navigationBarContrastEnforced Whether the system should ensure that the navigation
115 * bar has enough contrast when a fully transparent background is requested. Only supported on
116 * API 29+.
117 * @param transformColorForLightContent A lambda which will be invoked to transform [color] if
118 * dark icons were requested but are not available. Defaults to applying a black scrim.
119 *
120 * @see navigationBarDarkContentEnabled
121 * @see navigationBarContrastEnforced
122 */
123 public fun setNavigationBarColor(
124 color: Color,
125 darkIcons: Boolean = color.luminance() > 0.5f,
126 navigationBarContrastEnforced: Boolean = true,
127 transformColorForLightContent: (Color) -> Color = BlackScrimmed
128 )
129
130 /**
131 * Set the status and navigation bars to [color].
132 *
133 * @see setStatusBarColor
134 * @see setNavigationBarColor
135 */
136 public fun setSystemBarsColor(
137 color: Color,
138 darkIcons: Boolean = color.luminance() > 0.5f,
139 isNavigationBarContrastEnforced: Boolean = true,
140 transformColorForLightContent: (Color) -> Color = BlackScrimmed
141 ) {
142 setStatusBarColor(color, darkIcons, transformColorForLightContent)
143 setNavigationBarColor(
144 color,
145 darkIcons,
146 isNavigationBarContrastEnforced,
147 transformColorForLightContent
148 )
149 }
150
151 /**
152 * Property which holds whether the status bar icons + content are 'dark' or not.
153 */
154 public var statusBarDarkContentEnabled: Boolean
155
156 /**
157 * Property which holds whether the navigation bar icons + content are 'dark' or not.
158 */
159 public var navigationBarDarkContentEnabled: Boolean
160
161 /**
162 * Property which holds whether the status & navigation bar icons + content are 'dark' or not.
163 */
164 public var systemBarsDarkContentEnabled: Boolean
165 get() = statusBarDarkContentEnabled && navigationBarDarkContentEnabled
166 set(value) {
167 statusBarDarkContentEnabled = value
168 navigationBarDarkContentEnabled = value
169 }
170
171 /**
172 * Property which holds whether the system is ensuring that the navigation bar has enough
173 * contrast when a fully transparent background is requested. Only has an affect when running
174 * on Android API 29+ devices.
175 */
176 public var isNavigationBarContrastEnforced: Boolean
177 }
178
179 /**
180 * Remembers a [SystemUiController] for the given [window].
181 *
182 * If no [window] is provided, an attempt to find the correct [Window] is made.
183 *
184 * First, if the [LocalView]'s parent is a [DialogWindowProvider], then that dialog's [Window] will
185 * be used.
186 *
187 * Second, we attempt to find [Window] for the [Activity] containing the [LocalView].
188 *
189 * If none of these are found (such as may happen in a preview), then the functionality of the
190 * returned [SystemUiController] will be degraded, but won't throw an exception.
191 */
192 @Deprecated(
193 """
194 accompanist/systemuicontroller is deprecated and the API is no longer maintained.
195 We recommend going edge to edge using EdgeToEdge.enableEdgeToEdge in androidx.activity.
196 For more information please visit https://google.github.io/accompanist/systemuicontroller
197 """
198 )
199 @Composable
rememberSystemUiControllernull200 public fun rememberSystemUiController(
201 window: Window? = findWindow(),
202 ): SystemUiController {
203 val view = LocalView.current
204 return remember(view, window) { AndroidSystemUiController(view, window) }
205 }
206
207 @Composable
findWindownull208 private fun findWindow(): Window? =
209 (LocalView.current.parent as? DialogWindowProvider)?.window
210 ?: LocalView.current.context.findWindow()
211
212 private tailrec fun Context.findWindow(): Window? =
213 when (this) {
214 is Activity -> window
215 is ContextWrapper -> baseContext.findWindow()
216 else -> null
217 }
218
219 /**
220 * A helper class for setting the navigation and status bar colors for a [View], gracefully
221 * degrading behavior based upon API level.
222 *
223 * Typically you would use [rememberSystemUiController] to remember an instance of this.
224 */
225 internal class AndroidSystemUiController(
226 private val view: View,
227 private val window: Window?
228 ) : SystemUiController {
<lambda>null229 private val windowInsetsController = window?.let {
230 WindowCompat.getInsetsController(it, view)
231 }
232
setStatusBarColornull233 override fun setStatusBarColor(
234 color: Color,
235 darkIcons: Boolean,
236 transformColorForLightContent: (Color) -> Color
237 ) {
238 statusBarDarkContentEnabled = darkIcons
239
240 window?.statusBarColor = when {
241 darkIcons && windowInsetsController?.isAppearanceLightStatusBars != true -> {
242 // If we're set to use dark icons, but our windowInsetsController call didn't
243 // succeed (usually due to API level), we instead transform the color to maintain
244 // contrast
245 transformColorForLightContent(color)
246 }
247 else -> color
248 }.toArgb()
249 }
250
setNavigationBarColornull251 override fun setNavigationBarColor(
252 color: Color,
253 darkIcons: Boolean,
254 navigationBarContrastEnforced: Boolean,
255 transformColorForLightContent: (Color) -> Color
256 ) {
257 navigationBarDarkContentEnabled = darkIcons
258 isNavigationBarContrastEnforced = navigationBarContrastEnforced
259
260 window?.navigationBarColor = when {
261 darkIcons && windowInsetsController?.isAppearanceLightNavigationBars != true -> {
262 // If we're set to use dark icons, but our windowInsetsController call didn't
263 // succeed (usually due to API level), we instead transform the color to maintain
264 // contrast
265 transformColorForLightContent(color)
266 }
267 else -> color
268 }.toArgb()
269 }
270
271 override var systemBarsBehavior: Int
272 get() = windowInsetsController?.systemBarsBehavior ?: 0
273 set(value) {
274 windowInsetsController?.systemBarsBehavior = value
275 }
276
277 override var isStatusBarVisible: Boolean
278 get() {
279 return ViewCompat.getRootWindowInsets(view)
280 ?.isVisible(WindowInsetsCompat.Type.statusBars()) == true
281 }
282 set(value) {
283 if (value) {
284 windowInsetsController?.show(WindowInsetsCompat.Type.statusBars())
285 } else {
286 windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars())
287 }
288 }
289
290 override var isNavigationBarVisible: Boolean
291 get() {
292 return ViewCompat.getRootWindowInsets(view)
293 ?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true
294 }
295 set(value) {
296 if (value) {
297 windowInsetsController?.show(WindowInsetsCompat.Type.navigationBars())
298 } else {
299 windowInsetsController?.hide(WindowInsetsCompat.Type.navigationBars())
300 }
301 }
302
303 override var statusBarDarkContentEnabled: Boolean
304 get() = windowInsetsController?.isAppearanceLightStatusBars == true
305 set(value) {
306 windowInsetsController?.isAppearanceLightStatusBars = value
307 }
308
309 override var navigationBarDarkContentEnabled: Boolean
310 get() = windowInsetsController?.isAppearanceLightNavigationBars == true
311 set(value) {
312 windowInsetsController?.isAppearanceLightNavigationBars = value
313 }
314
315 override var isNavigationBarContrastEnforced: Boolean
316 get() = Build.VERSION.SDK_INT >= 29 && window?.isNavigationBarContrastEnforced == true
317 set(value) {
318 if (Build.VERSION.SDK_INT >= 29) {
319 window?.isNavigationBarContrastEnforced = value
320 }
321 }
322 }
323
324 private val BlackScrim = Color(0f, 0f, 0f, 0.3f) // 30% opaque black
originalnull325 private val BlackScrimmed: (Color) -> Color = { original ->
326 BlackScrim.compositeOver(original)
327 }
328