Skip to content
This repository was archived by the owner on Nov 28, 2023. It is now read-only.

Commit e38881a

Browse files
authored
Merge pull request #135 from tunjid/feature/insets-compat
Update to latest WindowInsetCompat APIs
2 parents 7c92303 + ec2f384 commit e38881a

File tree

5 files changed

+87
-77
lines changed

5 files changed

+87
-77
lines changed

app/src/main/java/com/tunjid/androidx/tabmisc/UiStatePlaygroundFragment.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ private var BindingViewHolder<ViewholderUiStateSliceBinding>.slice by viewHolder
111111

112112
private val Context.slices
113113
get() = listOf<Slice<*>>(
114+
Slice(
115+
name = "Status bar color",
116+
nameTransformer = Int::stringHex,
117+
options = listOf(Color.TRANSPARENT, Color.parseColor("#80000000"), Color.BLACK, Color.WHITE, Color.RED, Color.GREEN, Color.BLUE),
118+
getter = UiState::statusBarColor
119+
) {
120+
copy(statusBarColor = it)
121+
},
114122
Slice(
115123
name = "Has light status bar icons",
116124
options = listOf(true, false),

app/src/main/java/com/tunjid/androidx/uidrivers/GlobalUiDriver.kt

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.tunjid.androidx.uidrivers
22

33
import android.animation.ArgbEvaluator
44
import android.animation.ValueAnimator
5+
import android.annotation.SuppressLint
56
import android.graphics.Color
67
import android.graphics.drawable.GradientDrawable
78
import android.os.Build
@@ -14,6 +15,9 @@ import android.widget.EditText
1415
import androidx.annotation.ColorInt
1516
import androidx.annotation.DrawableRes
1617
import androidx.core.graphics.ColorUtils
18+
import androidx.core.view.WindowCompat
19+
import androidx.core.view.WindowInsetsCompat
20+
import androidx.core.view.WindowInsetsControllerCompat
1721
import androidx.core.view.doOnLayout
1822
import androidx.core.view.isVisible
1923
import androidx.core.view.updateLayoutParams
@@ -38,6 +42,7 @@ import com.tunjid.androidx.view.animator.ViewHider
3842
import com.tunjid.androidx.view.util.PaddingProperty
3943
import com.tunjid.androidx.view.util.innermostFocusedChild
4044
import com.tunjid.androidx.view.util.spring
45+
import com.tunjid.androidx.view.util.viewDelegate
4146
import com.tunjid.androidx.view.util.withOneShotEndListener
4247
import kotlin.math.max
4348

@@ -92,9 +97,10 @@ class GlobalUiDriver(
9297
private val uiSizes = UISizes(host)
9398
private val fabExtensionAnimator = FabExtensionAnimator(binding.fab)
9499
private val toolbarHider = ViewHider.of(binding.toolbar).setDirection(ViewHider.TOP).build()
100+
private val insetsController = WindowInsetsControllerCompat(host.window, binding.root)
95101
private val noOpInsetsListener = View.OnApplyWindowInsetsListener { _, insets -> insets }
96102
private val rootInsetsListener = View.OnApplyWindowInsetsListener { _, insets ->
97-
liveUiState.value = uiState.reduceSystemInsets(insets, uiSizes.navBarHeightThreshold)
103+
liveUiState.value = uiState.reduceSystemInsets(WindowInsetsCompat.toWindowInsetsCompat(insets), uiSizes.navBarHeightThreshold)
98104
// Consume insets so other views will not see them.
99105
insets.consumeSystemWindowInsets()
100106
}
@@ -144,6 +150,7 @@ class GlobalUiDriver(
144150

145151
UiState::snackbarText.distinct onChanged this::showSnackBar
146152
UiState::navBarColor.distinct onChanged this::setNavBarColor
153+
UiState::statusBarColor.distinct onChanged host.window::setStatusBarColor
147154
UiState::lightStatusBar.distinct onChanged this::setLightStatusBar
148155
UiState::fragmentContainerState.distinct onChanged this::updateFragmentContainer
149156
UiState::backgroundColor.distinct onChanged binding.contentRoot::animateBackground
@@ -221,39 +228,20 @@ class GlobalUiDriver(
221228
GradientDrawable.Orientation.BOTTOM_TOP,
222229
intArrayOf(color, Color.TRANSPARENT))
223230

224-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) uiFlagTweak {
225-
if (color.isBrightColor) it or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
226-
else it and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
227-
}
231+
insetsController.isAppearanceLightNavigationBars = color.isBrightColor
228232
}
229233

230-
private fun updateImmersivity(isImmersive: Boolean) = uiFlagTweak { systemUiFlags ->
231-
when (isImmersive) {
232-
true -> (systemUiFlags
233-
or View.SYSTEM_UI_FLAG_FULLSCREEN
234-
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
235-
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
236-
)
237-
false -> (systemUiFlags
238-
and View.SYSTEM_UI_FLAG_FULLSCREEN.inv()
239-
and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION.inv()
240-
and View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY.inv()
241-
)
242-
}
234+
private fun updateImmersivity(isImmersive: Boolean) {
235+
val systemBarsSetter = if (isImmersive) insetsController::hide else insetsController::show
236+
systemBarsSetter.invoke(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
237+
insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
243238
}
244239

245240
private fun setLightStatusBar(lightStatusBar: Boolean) = when {
246-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> uiFlagTweak { flags ->
247-
if (lightStatusBar) flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
248-
else flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
249-
}
241+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> insetsController.isAppearanceLightStatusBars = lightStatusBar
250242
else -> host.window.statusBarColor = host.colorAt(if (lightStatusBar) R.color.transparent else R.color.black_50)
251243
}
252244

253-
private fun uiFlagTweak(tweaker: (Int) -> Int) = host.window.decorView.run {
254-
systemUiVisibility = tweaker(systemUiVisibility)
255-
}
256-
257245
private fun setFabGlyphs(fabGlyphState: Pair<Int, CharSequence>) = host.runOnUiThread {
258246
val (@DrawableRes icon: Int, title: CharSequence) = fabGlyphState
259247
fabExtensionAnimator.updateGlyphs(title, if (icon != 0) host.drawableAt(icon) else null)
@@ -285,14 +273,17 @@ class GlobalUiDriver(
285273
private val <T : Any?> ((UiState) -> T).distinct get() = liveUiState.map(this).distinctUntilChanged()
286274
}
287275

276+
private var View.backgroundAnimator by viewDelegate<ValueAnimator?>()
277+
278+
@SuppressLint("Recycle")
288279
private fun View.animateBackground(@ColorInt to: Int) {
289-
val animator = getTag(R.id.doggo_image) as? ValueAnimator
290-
?: ValueAnimator().apply {
291-
setTag(R.id.doggo_image, this)
292-
setIntValues(Color.TRANSPARENT)
293-
setEvaluator(ArgbEvaluator())
294-
addUpdateListener { setBackgroundColor(it.animatedValue as Int) }
295-
}
280+
val animator = backgroundAnimator ?: ValueAnimator().apply {
281+
setTag(R.id.doggo_image, this)
282+
setIntValues(Color.TRANSPARENT)
283+
setEvaluator(ArgbEvaluator())
284+
addUpdateListener { setBackgroundColor(it.animatedValue as Int) }
285+
backgroundAnimator = this
286+
}
296287

297288
if (animator.isRunning) animator.cancel()
298289
animator.setIntValues(animator.animatedValue as Int, to)
@@ -324,12 +315,7 @@ private fun Window.assumeControl() {
324315
windowAttributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
325316
attributes = windowAttributes
326317
}
327-
decorView.systemUiVisibility = FULL_CONTROL_SYSTEM_UI_FLAGS
318+
WindowCompat.setDecorFitsSystemWindows(this, false)
328319
navigationBarColor = context.colorAt(R.color.transparent)
329320
statusBarColor = context.colorAt(R.color.transparent)
330321
}
331-
332-
private const val FULL_CONTROL_SYSTEM_UI_FLAGS =
333-
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
334-
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
335-
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

app/src/main/java/com/tunjid/androidx/uidrivers/SystemManagedUI.kt

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.tunjid.androidx.uidrivers
22

3-
import android.view.WindowInsets
3+
import androidx.core.graphics.Insets
4+
import androidx.core.view.WindowInsetsCompat
45

56
/**
67
* Interface for system managed bits of global ui that we react to, but do not explicitly request
@@ -20,10 +21,11 @@ interface StaticSystemUI {
2021
}
2122

2223
interface DynamicSystemUI {
23-
val leftInset: Int
24-
val topInset: Int
25-
val rightInset: Int
26-
val bottomInset: Int
24+
val statusBars: Insets
25+
val navBars: Insets
26+
val cutouts: Insets
27+
val captions: Insets
28+
val ime: Insets
2729
val snackbarHeight: Int
2830
}
2931

@@ -38,10 +40,11 @@ data class DelegateStaticSystemUI(
3840
) : StaticSystemUI
3941

4042
data class DelegateDynamicSystemUI internal constructor(
41-
override val leftInset: Int,
42-
override val topInset: Int,
43-
override val rightInset: Int,
44-
override val bottomInset: Int,
43+
override val statusBars: Insets,
44+
override val navBars: Insets,
45+
override val cutouts: Insets,
46+
override val captions: Insets,
47+
override val ime: Insets,
4548
override val snackbarHeight: Int
4649
) : DynamicSystemUI
4750

@@ -56,28 +59,35 @@ fun SystemUI.updateSnackbarHeight(snackbarHeight: Int) = when (this) {
5659
else -> this
5760
}
5861

59-
fun UiState.reduceSystemInsets(windowInsets: WindowInsets, navBarHeightThreshold: Int): UiState {
62+
fun UiState.reduceSystemInsets(windowInsets: WindowInsetsCompat, navBarHeightThreshold: Int): UiState {
6063
// Do this once, first call is the size
6164
val currentSystemUI = systemUI
6265
val currentStaticSystemUI = currentSystemUI.static
6366

67+
val statusBars = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars())
68+
val navBars = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
69+
val cutouts = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
70+
val captions = windowInsets.getInsets(WindowInsetsCompat.Type.captionBar())
71+
val ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
72+
6473
val updatedStaticUI = when {
6574
currentStaticSystemUI !is DelegateStaticSystemUI -> DelegateStaticSystemUI(
66-
statusBarSize = windowInsets.systemWindowInsetTop,
67-
navBarSize = windowInsets.systemWindowInsetBottom
75+
statusBarSize = statusBars.top,
76+
navBarSize = navBars.bottom
6877
)
69-
windowInsets.systemWindowInsetBottom < navBarHeightThreshold -> DelegateStaticSystemUI(
78+
navBars.bottom < navBarHeightThreshold -> DelegateStaticSystemUI(
7079
statusBarSize = currentStaticSystemUI.statusBarSize,
71-
navBarSize = windowInsets.systemWindowInsetBottom
80+
navBarSize = navBars.bottom
7281
)
7382
else -> currentStaticSystemUI
7483
}
7584

7685
val updatedDynamicUI = DelegateDynamicSystemUI(
77-
leftInset = windowInsets.systemWindowInsetLeft,
78-
topInset = windowInsets.systemWindowInsetTop,
79-
rightInset = windowInsets.systemWindowInsetRight,
80-
bottomInset = windowInsets.systemWindowInsetBottom,
86+
statusBars = statusBars,
87+
navBars = navBars,
88+
cutouts = cutouts,
89+
captions = captions,
90+
ime = ime,
8191
snackbarHeight = currentSystemUI.dynamic.snackbarHeight
8292
)
8393

@@ -102,14 +112,18 @@ private object NoOpStaticSystemUI : StaticSystemUI {
102112
}
103113

104114
private object NoOpDynamicSystemUI : DynamicSystemUI {
105-
override val leftInset: Int
106-
get() = 0
107-
override val topInset: Int
108-
get() = 0
109-
override val bottomInset: Int
110-
get() = 0
111-
override val rightInset: Int
112-
get() = 0
115+
override val statusBars: Insets
116+
get() = emptyInsets
117+
override val navBars: Insets
118+
get() = emptyInsets
119+
override val cutouts: Insets
120+
get() = emptyInsets
121+
override val captions: Insets
122+
get() = emptyInsets
123+
override val ime: Insets
124+
get() = emptyInsets
113125
override val snackbarHeight: Int
114126
get() = 0
115127
}
128+
129+
private val emptyInsets = Insets.of(0, 0, 0, 0)

app/src/main/java/com/tunjid/androidx/uidrivers/UiState.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.view.View
77
import androidx.annotation.ColorInt
88
import androidx.annotation.DrawableRes
99
import androidx.annotation.MenuRes
10+
import androidx.core.graphics.Insets
1011
import androidx.dynamicanimation.animation.SpringAnimation
1112
import androidx.lifecycle.Lifecycle
1213
import androidx.lifecycle.LifecycleOwner
@@ -16,6 +17,7 @@ import kotlin.reflect.KMutableProperty0
1617
fun KMutableProperty0<UiState>.updatePartial(updater: UiState.() -> UiState) = set(updater.invoke(get()))
1718

1819
data class UiState(
20+
val statusBarColor: Int = Color.TRANSPARENT,
1921
@param:MenuRes
2022
@field:MenuRes
2123
@get:MenuRes
@@ -69,7 +71,7 @@ internal data class ToolbarState(
6971

7072
internal data class SnackbarPositionalState(
7173
val bottomNavVisible: Boolean,
72-
override val bottomInset: Int,
74+
override val ime: Insets,
7375
override val navBarSize: Int,
7476
override val insetDescriptor: InsetDescriptor
7577
) : KeyboardAware
@@ -78,7 +80,7 @@ internal data class FabPositionalState(
7880
val fabVisible: Boolean,
7981
val bottomNavVisible: Boolean,
8082
val snackbarHeight: Int,
81-
override val bottomInset: Int,
83+
override val ime: Insets,
8284
override val navBarSize: Int,
8385
override val insetDescriptor: InsetDescriptor
8486
) : KeyboardAware
@@ -87,7 +89,7 @@ internal data class FragmentContainerPositionalState(
8789
val statusBarSize: Int,
8890
val toolbarOverlaps: Boolean,
8991
val bottomNavVisible: Boolean,
90-
override val bottomInset: Int,
92+
override val ime: Insets,
9193
override val navBarSize: Int,
9294
override val insetDescriptor: InsetDescriptor
9395
) : KeyboardAware
@@ -109,15 +111,15 @@ internal val UiState.fabState
109111
fabVisible = fabShows,
110112
snackbarHeight = systemUI.dynamic.snackbarHeight,
111113
bottomNavVisible = showsBottomNav == true,
112-
bottomInset = systemUI.dynamic.bottomInset,
114+
ime = systemUI.dynamic.ime,
113115
navBarSize = systemUI.static.navBarSize,
114116
insetDescriptor = insetFlags
115117
)
116118

117119
internal val UiState.snackbarPositionalState
118120
get() = SnackbarPositionalState(
119121
bottomNavVisible = showsBottomNav == true,
120-
bottomInset = systemUI.dynamic.bottomInset,
122+
ime = systemUI.dynamic.ime,
121123
navBarSize = systemUI.static.navBarSize,
122124
insetDescriptor = insetFlags
123125
)
@@ -137,11 +139,11 @@ internal val UiState.bottomNavPositionalState
137139

138140
internal val UiState.fragmentContainerState
139141
get() = FragmentContainerPositionalState(
140-
statusBarSize = systemUI.dynamic.topInset,
142+
statusBarSize = systemUI.static.statusBarSize,
141143
insetDescriptor = insetFlags,
142144
toolbarOverlaps = toolbarOverlaps,
143145
bottomNavVisible = showsBottomNav == true,
144-
bottomInset = systemUI.dynamic.bottomInset,
146+
ime = systemUI.dynamic.ime,
145147
navBarSize = systemUI.static.navBarSize
146148
)
147149

@@ -150,9 +152,9 @@ internal val UiState.fragmentContainerState
150152
* keyboard visibility changes for bottom aligned views like Floating Action Buttons and Snack Bars
151153
*/
152154
interface KeyboardAware {
153-
val bottomInset: Int
155+
val ime: Insets
154156
val navBarSize: Int
155157
val insetDescriptor: InsetDescriptor
156158
}
157159

158-
internal val KeyboardAware.keyboardSize get() = bottomInset - navBarSize
160+
internal val KeyboardAware.keyboardSize get() = ime.bottom - navBarSize

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ buildscript {
1616
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1717
}
1818
project.ext {
19-
buildToolsVersion = "29.0.2"
20-
compileSdkVersion = 29
21-
targetSdkVersion = 29
19+
buildToolsVersion = "29.0.3"
20+
compileSdkVersion = 30
21+
targetSdkVersion = 30
2222
minSdkVersion = 21
2323

2424
appCompat = 'androidx.appcompat:appcompat:1.3.0'

0 commit comments

Comments
 (0)