• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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