owenclave / app/src/main/java/io/nekohasekai/sagernet/ui/compose/components/ExpressiveDialog.kt

github - dev - back to tree - upstream
@file:OptIn(androidx.compose.material3.ExperimentalMaterial3ExpressiveApi::class) package io.nekohasekai.sagernet.ui.compose.components import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties /** * A distinctly M3E dialog rendered as a same-window [Popup] so the root content * behind it can be blurred (via [LocalScrimController]). Shows a dark scrim that * also covers the system bars, plus a bouncy scale-in surface. */ @Composable fun ExpressiveDialog( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit, ) { val scrim = LocalScrimController.current // Register so the root content blurs while this dialog is open. DisposableEffect(Unit) { scrim.push() onDispose { scrim.pop() } } val appear by animateFloatAsState( targetValue = 1f, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMedium, ), label = "dialogAppear", ) Popup( alignment = Alignment.Center, onDismissRequest = onDismissRequest, properties = PopupProperties(focusable = true), ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center, ) { // Dark scrim over everything (including behind the status bar). Box( modifier = Modifier .fillMaxSize() .background(Color.Black.copy(alpha = 0.45f)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null, ) { onDismissRequest() }, ) // The dialog surface, centered, scale + fade in. Surface( modifier = modifier .padding(24.dp) .widthIn(min = 280.dp, max = 400.dp) .graphicsLayer { alpha = appear scaleX = 0.9f + 0.1f * appear scaleY = 0.9f + 0.1f * appear }, shape = MaterialTheme.shapes.extraLarge, color = MaterialTheme.colorScheme.surfaceContainerHigh, tonalElevation = 6.dp, shadowElevation = 12.dp, ) { Column( modifier = Modifier.padding(24.dp), content = content, ) } } } }