diff --git a/android/app/src/main/java/com/thisux/droidclaw/overlay/VoiceOverlayContent.kt b/android/app/src/main/java/com/thisux/droidclaw/overlay/VoiceOverlayContent.kt new file mode 100644 index 0000000..b3a68e1 --- /dev/null +++ b/android/app/src/main/java/com/thisux/droidclaw/overlay/VoiceOverlayContent.kt @@ -0,0 +1,158 @@ +package com.thisux.droidclaw.overlay + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Send +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +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 + +private val AccentPurple = Color(0xFF8B5CF6) +private val PanelBackground = Color(0xCC1A1A1A) + +@Composable +fun VoiceOverlayContent( + transcript: String, + onSend: () -> Unit, + onCancel: () -> Unit +) { + val scrollState = rememberScrollState() + + LaunchedEffect(transcript) { + scrollState.animateScrollTo(scrollState.maxValue) + } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) + .background(PanelBackground) + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (transcript.isEmpty()) { + ListeningIndicator() + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Listening...", + color = Color.White.copy(alpha = 0.6f), + fontSize = 16.sp + ) + } else { + Text( + text = transcript, + color = Color.White, + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center, + lineHeight = 32.sp, + modifier = Modifier + .fillMaxWidth() + .height(160.dp) + .verticalScroll(scrollState) + .padding(horizontal = 8.dp) + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally) + ) { + OutlinedButton( + onClick = onCancel, + colors = ButtonDefaults.outlinedButtonColors( + contentColor = Color.White.copy(alpha = 0.7f) + ), + modifier = Modifier.weight(1f) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Text(" Cancel", fontSize = 15.sp) + } + + Button( + onClick = onSend, + enabled = transcript.isNotEmpty(), + colors = ButtonDefaults.buttonColors( + containerColor = AccentPurple, + contentColor = Color.White + ), + modifier = Modifier.weight(1f) + ) { + Icon( + imageVector = Icons.Default.Send, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Text(" Send", fontSize = 15.sp) + } + } + } + } +} + +@Composable +private fun ListeningIndicator() { + val transition = rememberInfiniteTransition(label = "listening") + val alpha by transition.animateFloat( + initialValue = 0.3f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(800, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "pulseAlpha" + ) + + Box( + modifier = Modifier + .size(48.dp) + .alpha(alpha) + .clip(CircleShape) + .background(AccentPurple) + ) +}