diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt index f08f448..991dbbe 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt @@ -18,289 +18,188 @@ package me.kavishdevar.librepods.composables -import android.graphics.RuntimeShader -import android.os.Build -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.VectorConverter -import androidx.compose.animation.core.VisibilityThreshold -import androidx.compose.animation.core.spring -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredWidthIn +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ShaderBrush -import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.util.fastCoerceAtMost -import androidx.compose.ui.util.fastCoerceIn -import androidx.compose.ui.util.lerp -import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.drawBackdrop -import com.kyant.backdrop.effects.blur -import com.kyant.backdrop.effects.colorControls -import com.kyant.backdrop.effects.refraction -import com.kyant.backdrop.highlight.Highlight -import kotlinx.coroutines.launch +import androidx.compose.ui.window.Dialog +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.utils.inspectDragGestures -import kotlin.math.abs -import kotlin.math.atan2 -import kotlin.math.cos -import kotlin.math.sin -import kotlin.math.tanh +@ExperimentalHazeMaterialsApi @Composable fun ConfirmationDialog( showDialog: MutableState, title: String, message: String, - confirmText: String = "Ok", + confirmText: String = "Enable", dismissText: String = "Cancel", onConfirm: () -> Unit, onDismiss: () -> Unit = { showDialog.value = false }, - backdrop: Backdrop, + hazeState: HazeState, ) { - AnimatedVisibility( - visible = showDialog.value, - enter = fadeIn() + scaleIn(initialScale = 1.25f), - exit = fadeOut() + scaleOut(targetScale = 0.9f) - ) { - val animationScope = rememberCoroutineScope() - val progressAnimation = remember { Animatable(0f) } - var pressStartPosition by remember { mutableStateOf(Offset.Zero) } - val offsetAnimation = remember { Animatable(Offset.Zero, Offset.VectorConverter) } - - val interactiveHighlightShader = remember { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - RuntimeShader( - """ -uniform float2 size; -layout(color) uniform half4 color; -uniform float radius; -uniform float2 offset; - -half4 main(float2 coord) { - float2 center = offset; - float dist = distance(coord, center); - float intensity = smoothstep(radius, radius * 0.5, dist); - return color * intensity; -}""" - ) - } else { - null - } - } - - val isLightTheme = !isSystemInDarkTheme() - val contentColor = if (isLightTheme) Color.Black else Color.White - val accentColor = if (isLightTheme) Color(0xFF0088FF) else Color(0xFF0091FF) - val containerColor = if (isLightTheme) Color(0xFFFFFFFF).copy(0.6f) else Color(0xFF101010).copy(0.6f) - - Box( - Modifier - .fillMaxSize() - .clickable(onClick = onDismiss, indication = null, interactionSource = remember { MutableInteractionSource() } ) - ) { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val accentColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) + if (showDialog.value) { + Dialog(onDismissRequest = { showDialog.value = false }) { Box( - Modifier - .align(Alignment.Center) - .clickable(onClick = {}, indication = null, interactionSource = remember { MutableInteractionSource() } ) - .drawBackdrop( - backdrop, - { RoundedCornerShape(48f.dp) }, - highlight = { - Highlight.SolidDefault - }, - onDrawSurface = { drawRect(containerColor) }, - effects = { - colorControls( - brightness = if (isLightTheme) 0.4f else 0.2f, - saturation = 1.5f - ) - blur(if (isLightTheme) 16f.dp.toPx() else 8f.dp.toPx()) - refraction(24f.dp.toPx(), 48f.dp.toPx(), true) - }, - layerBlock = { - val width = size.width - val height = size.height - - val progress = progressAnimation.value - val maxScale = 0f - val scale = lerp(1f, 1f + maxScale, progress) - - val maxOffset = size.minDimension - val initialDerivative = 0.05f - val offset = offsetAnimation.value - translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) - translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) - - val maxDragScale = 0.1f - val offsetAngle = atan2(offset.y, offset.x) - scaleX = - scale + - maxDragScale * abs(cos(offsetAngle) * offset.x / size.maxDimension) * - (width / height).fastCoerceAtMost(1f) - scaleY = - scale + - maxDragScale * abs(sin(offsetAngle) * offset.y / size.maxDimension) * - (height / width).fastCoerceAtMost(1f) - }, - onDrawFront = { - val progress = progressAnimation.value.fastCoerceIn(0f, 1f) - if (progress > 0f) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && interactiveHighlightShader != null) { - drawRect( - Color.White.copy(0.05f * progress), - blendMode = BlendMode.Plus - ) - interactiveHighlightShader.apply { - val offset = pressStartPosition + offsetAnimation.value - setFloatUniform("size", size.width, size.height) - setColorUniform("color", Color.White.copy(0.075f * progress).toArgb()) - setFloatUniform("radius", size.maxDimension / 2) - setFloatUniform( - "offset", - offset.x.fastCoerceIn(0f, size.width), - offset.y.fastCoerceIn(0f, size.height) - ) - } - drawRect( - ShaderBrush(interactiveHighlightShader), - blendMode = BlendMode.Plus - ) - } else { - drawRect( - Color.White.copy(0.125f * progress), - blendMode = BlendMode.Plus - ) - } - } - }, - contentEffects = { - refraction(8f.dp.toPx(), 24f.dp.toPx(), false) - } - ) - .fillMaxWidth(0.75f) + modifier = Modifier + // .fillMaxWidth(0.75f) .requiredWidthIn(min = 200.dp, max = 360.dp) - .pointerInput(animationScope) { - val progressAnimationSpec = spring(0.5f, 300f, 0.001f) - val offsetAnimationSpec = spring(1f, 300f, Offset.VisibilityThreshold) - val onDragStop: () -> Unit = { - animationScope.launch { - launch { progressAnimation.animateTo(0f, progressAnimationSpec) } - launch { offsetAnimation.animateTo(Offset.Zero, offsetAnimationSpec) } - } - } - inspectDragGestures( - onDragStart = { down -> - pressStartPosition = down.position - animationScope.launch { - launch { progressAnimation.animateTo(1f, progressAnimationSpec) } - launch { offsetAnimation.snapTo(Offset.Zero) } - } - }, - onDragEnd = { onDragStop() }, - onDragCancel = onDragStop - ) { _, dragAmount -> - animationScope.launch { - offsetAnimation.snapTo(offsetAnimation.value + dragAmount) - } - } - } + .background(Color.Transparent, RoundedCornerShape(14.dp)) + .clip(RoundedCornerShape(14.dp)) + .hazeEffect( + hazeState, + style = CupertinoMaterials.regular( + containerColor = if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.95f) else Color.White.copy(alpha = 0.95f) + ) + ) ) { - Column(horizontalAlignment = Alignment.Start) { - Spacer(modifier = Modifier.height(28.dp)) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(24.dp)) Text( title, style = TextStyle( - fontSize = 18.sp, + fontSize = 16.sp, fontWeight = FontWeight.Bold, - color = contentColor, + color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)) ), + textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp) ) - Spacer(modifier = Modifier.height(16.dp)) + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(12.dp)) Text( message, style = TextStyle( fontSize = 14.sp, - color = contentColor.copy(alpha = 0.8f), + color = textColor.copy(alpha = 0.8f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), + textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp) ) - Spacer(modifier = Modifier.height(16.dp)) - + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(16.dp)) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier.fillMaxWidth() + ) + var leftPressed by remember { mutableStateOf(false) } + var rightPressed by remember { mutableStateOf(false) } + val pressedColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) Row( - Modifier - .padding(horizontal = 12.dp) - .padding(top = 12.dp, bottom = 24.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + val position = event.changes.first().position + val width = size.width.toFloat() + val height = size.height.toFloat() + val isWithinBounds = position.y >= 0 && position.y <= height + val isLeft = position.x < width / 2 + event.changes.first().consume() + when (event.type) { + PointerEventType.Press -> { + if (isWithinBounds) { + leftPressed = isLeft + rightPressed = !isLeft + } else { + leftPressed = false + rightPressed = false + } + } + PointerEventType.Move -> { + if (isWithinBounds) { + leftPressed = isLeft + rightPressed = !isLeft + } else { + leftPressed = false + rightPressed = false + } + } + PointerEventType.Release -> { + if (isWithinBounds) { + if (leftPressed) { + onDismiss() + } else if (rightPressed) { + onConfirm() + } + } + leftPressed = false + rightPressed = false + } + } + } + } + }, + horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically ) { - StyledButton( - onClick = onDismiss, - backdrop = backdrop, - surfaceColor = if (isLightTheme) Color(0xFFAAAAAA).copy(0.8f) else Color(0xFF202020).copy(0.8f), - modifier = Modifier.weight(1f), - isInteractive = false + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .background(if (leftPressed) pressedColor else Color.Transparent), + contentAlignment = Alignment.Center ) { - Text( - dismissText, - style = TextStyle(contentColor, 16.sp) - ) + Text(dismissText, color = accentColor) } - StyledButton( - onClick = onConfirm, - backdrop = backdrop, - surfaceColor = accentColor, - modifier = Modifier.weight(1f), - isInteractive = false + Box( + modifier = Modifier + .width(1.dp) + .fillMaxHeight() + .background(Color(0x40888888)) + ) + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .background(if (rightPressed) pressedColor else Color.Transparent), + contentAlignment = Alignment.Center ) { - Text( - confirmText, - style = TextStyle(Color.White, 16.sp) - ) + Text(confirmText, color = accentColor) } } } } } } -} +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 9bdb695..b036db9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -287,6 +287,7 @@ fun HearingAidScreen(navController: NavController) { } } }, - backdrop = backdrop + hazeState = hazeState, + // backdrop = backdrop ) }