Remove corner clip, draw content behind system bars

This commit is contained in:
MM20 2022-06-11 21:12:01 +02:00
parent e565780015
commit c7c97a3866
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
5 changed files with 299 additions and 41 deletions

View File

@ -14,9 +14,11 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle
@ -111,7 +113,6 @@ class LauncherActivity : BaseActivity() {
PullDownScaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.graphicsLayer {
scaleX = 0.5f + enterTransition.value * 0.5f
scaleY = 0.5f + enterTransition.value * 0.5f
@ -125,7 +126,6 @@ class LauncherActivity : BaseActivity() {
PagerScaffold(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.graphicsLayer {
scaleX = enterTransition.value
scaleY = enterTransition.value

View File

@ -22,21 +22,17 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.toPixels
@ -61,7 +57,6 @@ fun PagerScaffold(
) {
val viewModel: LauncherScaffoldVM = viewModel()
val searchVM: SearchVM = viewModel()
val context = LocalContext.current
val isSearchOpen by viewModel.isSearchOpen.observeAsState(false)
val isWidgetEditMode by viewModel.isWidgetEditMode.observeAsState(false)
@ -70,6 +65,25 @@ fun PagerScaffold(
val searchScrollState = rememberScrollState()
val swipeableState = rememberSwipeableState(if (isSearchOpen) Page.Search else Page.Widgets)
val showStatusBarScrim by remember {
derivedStateOf {
if (isSearchOpen) {
searchScrollState.value < searchScrollState.maxValue
} else {
widgetsScrollState.value > 0
}
}
}
val showNavBarScrim by remember {
derivedStateOf {
if (isSearchOpen) {
searchScrollState.value > 0
} else {
widgetsScrollState.value > 0 && widgetsScrollState.value < widgetsScrollState.maxValue
}
}
}
val isWidgetsScrollZero by remember {
derivedStateOf {
widgetsScrollState.value == 0
@ -79,11 +93,15 @@ fun PagerScaffold(
val systemUiController = rememberSystemUiController()
val colorSurface = MaterialTheme.colorScheme.surface
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface) {
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface, showStatusBarScrim) {
if (isWidgetEditMode) {
systemUiController.setStatusBarColor(
colorSurface
)
} else if (showStatusBarScrim) {
systemUiController.setStatusBarColor(
colorSurface.copy(0.75f),
)
} else {
systemUiController.setStatusBarColor(
Color.Transparent,
@ -92,12 +110,18 @@ fun PagerScaffold(
}
}
LaunchedEffect(darkNavBarIcons) {
systemUiController.setNavigationBarColor(
Color.Transparent,
darkIcons = darkNavBarIcons,
navigationBarContrastEnforced = false
)
LaunchedEffect(darkNavBarIcons, showNavBarScrim) {
if (showNavBarScrim) {
systemUiController.setNavigationBarColor(
colorSurface.copy(0.75f),
)
} else {
systemUiController.setNavigationBarColor(
Color.Transparent,
darkIcons = darkNavBarIcons,
navigationBarContrastEnforced = false
)
}
}
val blurWallpaper by remember {
@ -138,11 +162,11 @@ fun PagerScaffold(
}
}
val notificationDragThreshold = with(LocalDensity.current) {200.dp.toPx()}
val notificationDragThreshold = with(LocalDensity.current) { 200.dp.toPx() }
val notificationShadeController = rememberNotificationShadeController()
val nestedScrollConnection = remember {
object: NestedScrollConnection {
object : NestedScrollConnection {
private var pullDownTotalY: Float? = 0f
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val diff = widgetsScrollState.value - available.y
@ -171,6 +195,8 @@ fun PagerScaffold(
}
}
val insets = WindowInsets.systemBars.asPaddingValues()
Box(
modifier = modifier
) {
@ -218,12 +244,12 @@ fun PagerScaffold(
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
val clockPadding by animateDpAsState(
if (isWidgetsScrollZero) 64.dp else 0.dp
if (isWidgetsScrollZero) 64.dp + insets.calculateBottomPadding() else 0.dp
)
val clockHeight by remember {
derivedStateOf {
height - (64.dp - clockPadding)
height - (64.dp + insets.calculateTopPadding() + insets.calculateBottomPadding() - clockPadding)
}
}
@ -231,10 +257,10 @@ fun PagerScaffold(
modifier = Modifier
.requiredWidth(width)
.fillMaxHeight()
.padding(horizontal = 8.dp)
.clip(MaterialTheme.shapes.medium)
.nestedScroll(nestedScrollConnection)
.verticalScroll(widgetsScrollState)
.systemBarsPadding()
.padding(horizontal = 8.dp)
.padding(top = 8.dp, bottom = 64.dp)
.padding(top = editModePadding),
clockHeight = { clockHeight },
@ -254,10 +280,10 @@ fun PagerScaffold(
modifier = Modifier
.requiredWidth(width)
.fillMaxHeight()
.padding(horizontal = 8.dp)
.clip(MaterialTheme.shapes.medium)
.verticalScroll(searchScrollState, reverseScrolling = true)
.imePadding()
.systemBarsPadding()
.padding(horizontal = 8.dp)
.padding(top = 8.dp, bottom = 64.dp)
.padding(bottom = webSearchPadding),
reverse = true,
@ -302,6 +328,7 @@ fun PagerScaffold(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp)
.systemBarsPadding()
.imePadding()
.offset(y = widgetEditModeOffset),
level = { searchBarLevel }, focused = focusSearchBar, onFocusChange = {

View File

@ -1,6 +1,5 @@
package de.mm20.launcher2.ui.launcher
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
@ -19,7 +18,6 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin
@ -43,6 +41,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
import de.mm20.launcher2.ui.launcher.search.SearchColumn
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
import de.mm20.launcher2.ui.modifier.verticalFadingEdges
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@ -55,7 +54,6 @@ fun PullDownScaffold(
) {
val viewModel: LauncherScaffoldVM = viewModel()
val searchVM: SearchVM = viewModel()
val context = LocalContext.current
val density = LocalDensity.current
@ -67,12 +65,41 @@ fun PullDownScaffold(
val systemUiController = rememberSystemUiController()
val isWidgetsScrollZero by remember {
derivedStateOf {
widgetsScrollState.value == 0
}
}
val showStatusBarScrim by remember {
derivedStateOf {
if (isSearchOpen) {
searchScrollState.value > 0
} else {
widgetsScrollState.value > 0
}
}
}
val showNavBarScrim by remember {
derivedStateOf {
if (isSearchOpen) {
searchScrollState.value < searchScrollState.maxValue
} else {
widgetsScrollState.value > 0 && widgetsScrollState.value < widgetsScrollState.maxValue
}
}
}
val colorSurface = MaterialTheme.colorScheme.surface
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface) {
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface, showStatusBarScrim) {
if (isWidgetEditMode) {
systemUiController.setStatusBarColor(
colorSurface
)
} else if (showStatusBarScrim) {
systemUiController.setStatusBarColor(
colorSurface.copy(0.75f),
)
} else {
systemUiController.setStatusBarColor(
Color.Transparent,
@ -81,12 +108,18 @@ fun PullDownScaffold(
}
}
LaunchedEffect(darkNavBarIcons) {
systemUiController.setNavigationBarColor(
Color.Transparent,
darkIcons = darkNavBarIcons,
navigationBarContrastEnforced = false
)
LaunchedEffect(darkNavBarIcons, showNavBarScrim) {
if (showNavBarScrim) {
systemUiController.setNavigationBarColor(
colorSurface.copy(0.75f),
)
} else {
systemUiController.setNavigationBarColor(
Color.Transparent,
darkIcons = darkNavBarIcons,
navigationBarContrastEnforced = false
)
}
}
val offsetY = remember { mutableStateOf(0f) }
@ -151,7 +184,7 @@ fun PullDownScaffold(
consumed
}
isSearchOpen && (offsetY.value < 0 || source == NestedScrollSource.Drag && newValue > searchScrollState.maxValue) -> {
val consumed = available.y - (value- searchScrollState.maxValue)
val consumed = available.y - (value - searchScrollState.maxValue)
offsetY.value = (offsetY.value + (consumed * 0.5f)).coerceIn(-maxOffset, 0f)
consumed
}
@ -182,11 +215,13 @@ fun PullDownScaffold(
}
}
val insets = WindowInsets.systemBars.asPaddingValues()
Box(
modifier = modifier
.padding(horizontal = 8.dp)
.clip(MaterialTheme.shapes.medium)
.verticalFadingEdges(
top = insets.calculateTopPadding(),
amount = 0.85f
)
.nestedScroll(nestedScrollConnection)
.offset { IntOffset(0, offsetY.value.toInt()) },
contentAlignment = Alignment.TopCenter
@ -230,12 +265,21 @@ fun PullDownScaffold(
.fillMaxWidth()
.requiredHeight(height)
.verticalScroll(searchScrollState)
.padding(vertical = 8.dp)
.systemBarsPadding()
.padding(8.dp)
.padding(top = 56.dp)
.padding(top = webSearchPadding)
.imePadding()
)
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
val clockPadding by animateDpAsState(
if (isWidgetsScrollZero) insets.calculateBottomPadding() else 0.dp
)
val clockHeight by remember {
derivedStateOf {
height - (insets.calculateTopPadding() + insets.calculateBottomPadding() - clockPadding)
}
}
WidgetColumn(
modifier =
Modifier
@ -248,9 +292,11 @@ fun PullDownScaffold(
.fillMaxWidth()
.requiredHeight(height)
.verticalScroll(widgetsScrollState)
.padding(vertical = 8.dp)
.systemBarsPadding()
.padding(8.dp)
.padding(top = editModePadding),
clockHeight = { height },
clockHeight = { clockHeight },
clockBottomPadding = { clockPadding },
editMode = isWidgetEditMode,
onEditModeChange = {
viewModel.setWidgetEditMode(it)
@ -268,6 +314,8 @@ fun PullDownScaffold(
exit = slideOut { IntOffset(0, -it.height) }
) {
CenterAlignedTopAppBar(
modifier = Modifier
.systemBarsPadding(),
title = {
Text(stringResource(R.string.menu_edit_widgets))
},
@ -285,7 +333,7 @@ fun PullDownScaffold(
offsetY.value != 0f -> SearchBarLevel.Raised
isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active
isSearchOpen && searchScrollState.value > 0 -> SearchBarLevel.Raised
widgetsScrollState.value > 0 -> SearchBarLevel.Raised
!isWidgetsScrollZero -> SearchBarLevel.Raised
else -> SearchBarLevel.Resting
}
}
@ -300,7 +348,8 @@ fun PullDownScaffold(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(vertical = 8.dp)
.systemBarsPadding()
.padding(8.dp)
.offset { IntOffset(0, searchBarOffset.value.toInt()) }
.offset {
IntOffset(

View File

@ -0,0 +1,89 @@
package de.mm20.launcher2.ui.modifier
import androidx.annotation.FloatRange
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.pow
fun Modifier.verticalFadingEdges(
enabled: Boolean = true,
top: Dp = 0.dp,
bottom: Dp = 0.dp,
/**
* How strong the fading effect should be.
* If 1, edges will be completely transparent.
* If 0, the modifier will have no effect at all.
*/
@FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true) amount: Float = 1f
): Modifier {
if(!enabled) return this
if (top == 0.dp && bottom == 0.dp) return this
return this then drawWithContent {
val topColors = if (top > 0.dp) createColors(
1f - amount,
top.roundToPx() + 1,
) else emptyList()
val bottomColors = if (bottom > 0.dp) createColors(
1f - amount,
bottom.roundToPx() + 1,
reverse = true
) else emptyList()
val topSteps = if (top > 0.dp) createColorSteps(
size.height,
top.toPx() * 1.3f,
top.roundToPx() + 1
) else emptyList()
val bottomSteps = if (bottom > 0.dp) createColorSteps(
size.height,
bottom.toPx() * 1.3f,
bottom.roundToPx() + 1,
reverse = true
) else emptyList()
val paint = Paint().apply {
blendMode = BlendMode.DstIn
shader = LinearGradientShader(
Offset.Zero,
Offset(0f, size.height),
colors = topColors + bottomColors,
colorStops = topSteps + bottomSteps
)
}
drawContent()
drawIntoCanvas {
it.drawRect(
Rect(0f, 0f, size.width, size.height),
paint
)
}
}
}
private fun createColors(alpha: Float, steps: Int, reverse: Boolean = false): List<Color> {
val interval = 1f / (steps - 1)
return (0 until steps).map {
val x = interval * if (reverse) (steps - 1 - it) else it
val y = (1 - alpha) * (1f - (x - 1f).pow(2)) + alpha
Color.Black.copy(alpha = y)
}
}
private fun createColorSteps(height: Float, size: Float, steps: Int, reverse: Boolean = false): List<Float> {
val interval = 1f / (steps - 1)
return (0 until steps).map {
val x = interval * if (reverse) (steps - 1 - it) else it
if (reverse) 1 - (x * size / height) else (x * size / height)
}
}

View File

@ -0,0 +1,93 @@
package de.mm20.launcher2.ui.modifier
import androidx.annotation.FloatRange
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.pow
fun Modifier.verticalScrims(
enabled: Boolean = true,
top: Dp = 0.dp,
bottom: Dp = 0.dp,
/**
* How strong the fading effect should be.
* If 1, edges will be completely transparent.
* If 0, the modifier will have no effect at all.
*/
@FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true) amount: Float = 1f
): Modifier {
if (!enabled) return this
if (top == 0.dp && bottom == 0.dp) return this
return this then drawWithCache {
onDrawWithContent {
val topColors = if (top > 0.dp) createColors(
1f - amount,
top.roundToPx() + 1,
) else emptyList()
val bottomColors = if (bottom > 0.dp) createColors(
1f - amount,
bottom.roundToPx() + 1,
reverse = true
) else emptyList()
val topSteps = if (top > 0.dp) createColorSteps(
size.height,
top.toPx() * 1.3f,
top.roundToPx() + 1
) else emptyList()
val bottomSteps = if (bottom > 0.dp) createColorSteps(
size.height,
bottom.toPx() * 1.3f,
bottom.roundToPx() + 1,
reverse = true
) else emptyList()
val paint = Paint().apply {
shader = LinearGradientShader(
Offset.Zero,
Offset(0f, size.height),
colors = topColors + bottomColors,
colorStops = topSteps + bottomSteps
)
}
drawContent()
drawIntoCanvas {
it.drawRect(
Rect(0f, 0f, size.width, size.height),
paint
)
}
}
}
}
private fun createColors(alpha: Float, steps: Int, reverse: Boolean = false): List<Color> {
val interval = 1f / (steps - 1)
return (0 until steps).map {
val x = interval * if (reverse) (steps - 1 - it) else it
val y = 1f - ((1 - alpha) * (1f - (x - 1f).pow(2)) + alpha)
Color.Black.copy(alpha = y)
}
}
private fun createColorSteps(
height: Float,
size: Float,
steps: Int,
reverse: Boolean = false
): List<Float> {
val interval = 1f / (steps - 1)
return (0 until steps).map {
val x = interval * if (reverse) (steps - 1 - it) else it
if (reverse) 1 - (x * size / height) else (x * size / height)
}
}