Add control center expanding gesture
This commit is contained in:
1 parent
256331232b
commit
370cb51cb5
6 files changed
+196
-34
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
package com.kyant.backdrop.catalog
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.exp
|
||||
import kotlin.math.sign
|
||||
|
||||
fun interface ProgressConverter {
|
||||
|
||||
fun convert(progress: Float): Float
|
||||
|
||||
companion object {
|
||||
|
||||
val Default: ProgressConverter =
|
||||
ProgressConverter { progress ->
|
||||
(1f - exp(-abs(progress))) * progress.sign
|
||||
}
|
||||
}
|
||||
}
|
||||
+170
-26
@@ -1,8 +1,14 @@
|
||||
package com.kyant.backdrop.catalog.destinations
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.EaseIn
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -15,19 +21,29 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.draw.paint
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.BlurEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastCoerceAtMost
|
||||
import androidx.compose.ui.util.lerp
|
||||
import com.kyant.backdrop.BackdropEffectScope
|
||||
import com.kyant.backdrop.backdrop
|
||||
import com.kyant.backdrop.catalog.ProgressConverter
|
||||
import com.kyant.backdrop.catalog.R
|
||||
import com.kyant.backdrop.catalog.rememberUISensor
|
||||
import com.kyant.backdrop.catalog.theme.LocalContentColor
|
||||
@@ -40,6 +56,7 @@ import com.kyant.backdrop.highlight.HighlightStyle
|
||||
import com.kyant.backdrop.rememberLayerBackdrop
|
||||
import com.kyant.capsule.ContinuousCapsule
|
||||
import com.kyant.capsule.ContinuousRoundedRectangle
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ControlCenterContent() {
|
||||
@@ -67,38 +84,88 @@ fun ControlCenterContent() {
|
||||
|
||||
val backdrop = rememberLayerBackdrop(null)
|
||||
val uiSensor = rememberUISensor()
|
||||
val glassShape = { itemShape }
|
||||
val glassHighlight = { Highlight { HighlightStyle.Dynamic(angle = uiSensor.gravityAngle) } }
|
||||
val glassSurface: DrawScope.() -> Unit = { drawRect(containerColor) }
|
||||
val glassEffects: BackdropEffectScope.() -> Unit = {
|
||||
saturation()
|
||||
blur(16f.dp.toPx())
|
||||
refraction(24f.dp.toPx(), 48f.dp.toPx(), true)
|
||||
}
|
||||
|
||||
fun Modifier.glass() =
|
||||
this
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
{ itemShape },
|
||||
highlight = { Highlight { HighlightStyle.Dynamic(angle = uiSensor.gravityAngle) } },
|
||||
onDrawSurface = { drawRect(containerColor) }
|
||||
) {
|
||||
saturation()
|
||||
blur(16f.dp.toPx())
|
||||
refraction(24f.dp.toPx(), 48f.dp.toPx(), true)
|
||||
val animationScope = rememberCoroutineScope()
|
||||
val enterProgressAnimation = remember { Animatable(1f) }
|
||||
val progress by remember {
|
||||
derivedStateOf {
|
||||
val progress = enterProgressAnimation.value
|
||||
when {
|
||||
progress < 0f -> ProgressConverter.Default.convert(progress)
|
||||
progress <= 1f -> progress
|
||||
else -> 1f + ProgressConverter.Default.convert(progress - 1f)
|
||||
}
|
||||
}
|
||||
}
|
||||
val maxDragHeight = 1000f
|
||||
|
||||
CompositionLocalProvider(LocalContentColor provides Color.White) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Box(
|
||||
Modifier
|
||||
.draggable(
|
||||
rememberDraggableState { delta ->
|
||||
val targetProgress = enterProgressAnimation.value + delta / maxDragHeight
|
||||
animationScope.launch { enterProgressAnimation.snapTo(targetProgress) }
|
||||
},
|
||||
Orientation.Vertical,
|
||||
onDragStopped = { velocity ->
|
||||
val targetProgress = when {
|
||||
velocity < 0f -> 0f
|
||||
velocity > 0f -> 1f
|
||||
else -> if (enterProgressAnimation.value < 0.5f) 0f else 1f
|
||||
}
|
||||
animationScope.launch {
|
||||
enterProgressAnimation.animateTo(
|
||||
targetProgress,
|
||||
if (targetProgress > 0.5f) {
|
||||
spring(0.5f, 300f, 0.001f)
|
||||
} else {
|
||||
spring(1f, 300f, 0.01f)
|
||||
},
|
||||
velocity / maxDragHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Image(
|
||||
painterResource(R.drawable.system_home_screen_light),
|
||||
null,
|
||||
Modifier
|
||||
.backdrop(backdrop)
|
||||
.drawWithContent {
|
||||
val progress = progress
|
||||
|
||||
drawContent()
|
||||
drawRect(dimColor)
|
||||
drawRect(dimColor.copy(dimColor.alpha * progress.fastCoerceAtMost(1f)))
|
||||
}
|
||||
.graphicsLayer {
|
||||
val progress = progress
|
||||
|
||||
val blurRadius = 8f.dp.toPx() * progress.fastCoerceAtMost(1f)
|
||||
renderEffect = BlurEffect(blurRadius, blurRadius)
|
||||
}
|
||||
.blur(8f.dp)
|
||||
.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.graphicsLayer {
|
||||
val progress = progress
|
||||
|
||||
translationY = lerp(-56f.dp.toPx(), 0f, progress)
|
||||
alpha = EaseIn.transform(progress)
|
||||
}
|
||||
.padding(top = 80f.dp)
|
||||
.systemBarsPadding()
|
||||
.displayCutoutPadding()
|
||||
@@ -112,7 +179,14 @@ fun ControlCenterContent() {
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.size(itemTwoSpanSize)
|
||||
.padding(itemSpacing)
|
||||
) {
|
||||
@@ -149,7 +223,14 @@ fun ControlCenterContent() {
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.size(itemTwoSpanSize)
|
||||
)
|
||||
}
|
||||
@@ -167,14 +248,28 @@ fun ControlCenterContent() {
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.clickable {}
|
||||
.paint(airplaneModeIcon, colorFilter = iconColorFilter)
|
||||
.size(itemSize)
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.clickable {}
|
||||
.paint(airplaneModeIcon, colorFilter = iconColorFilter)
|
||||
.size(itemSize)
|
||||
@@ -182,7 +277,14 @@ fun ControlCenterContent() {
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.size(itemTwoSpanSize, itemSize)
|
||||
)
|
||||
}
|
||||
@@ -193,12 +295,26 @@ fun ControlCenterContent() {
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.size(itemSize, itemTwoSpanSize)
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.size(itemSize, itemTwoSpanSize)
|
||||
)
|
||||
}
|
||||
@@ -210,7 +326,14 @@ fun ControlCenterContent() {
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.size(itemTwoSpanSize)
|
||||
)
|
||||
|
||||
@@ -223,14 +346,28 @@ fun ControlCenterContent() {
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.clickable {}
|
||||
.paint(airplaneModeIcon, colorFilter = iconColorFilter)
|
||||
.size(itemSize)
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.clickable {}
|
||||
.paint(airplaneModeIcon, colorFilter = iconColorFilter)
|
||||
.size(itemSize)
|
||||
@@ -242,7 +379,14 @@ fun ControlCenterContent() {
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.glass()
|
||||
.drawBackdrop(
|
||||
backdrop,
|
||||
glassShape,
|
||||
highlight = glassHighlight,
|
||||
shadow = null,
|
||||
onDrawSurface = glassSurface,
|
||||
effects = glassEffects
|
||||
)
|
||||
.clickable {}
|
||||
.paint(airplaneModeIcon, colorFilter = iconColorFilter)
|
||||
.size(itemSize)
|
||||
|
||||
@@ -33,14 +33,6 @@ fun HomeContent(onNavigate: (CatalogDestination) -> Unit) {
|
||||
Modifier.padding(16f.dp, 40f.dp, 16f.dp, 16f.dp)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
"Dialog",
|
||||
TextStyles.body,
|
||||
Modifier
|
||||
.clickable { onNavigate(CatalogDestination.Dialog) }
|
||||
.padding(16f.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
"Control center",
|
||||
TextStyles.body,
|
||||
@@ -49,6 +41,14 @@ fun HomeContent(onNavigate: (CatalogDestination) -> Unit) {
|
||||
.padding(16f.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
Text(
|
||||
"Dialog",
|
||||
TextStyles.body,
|
||||
Modifier
|
||||
.clickable { onNavigate(CatalogDestination.Dialog) }
|
||||
.padding(16f.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user