Skip to content

Instantly share code, notes, and snippets.

@ardakazanci
Created July 27, 2025 12:05
Show Gist options
  • Save ardakazanci/3f55b595043274e6c61fcb69ea9c4340 to your computer and use it in GitHub Desktop.
Save ardakazanci/3f55b595043274e6c61fcb69ea9c4340 to your computer and use it in GitHub Desktop.
Keyboard Animation Insets with Jetpack Compose
@Composable
fun KeyboardWindowInsets(modifier: Modifier = Modifier) {
val coroutineScope = rememberCoroutineScope()
val verticalOffset = remember { Animatable(0f) }
var email by remember { mutableStateOf("") }
var sliderValue by remember { mutableFloatStateOf(1f) }
val isValidEmail = remember(email) {
Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
val showCard = email.isNotEmpty()
val backgroundColor by animateColorAsState(
if (isValidEmail) Color(0xFF00C853) else Color(0xFFD32F2F),
label = "background"
)
val icon = if (isValidEmail) Icons.Default.CheckCircle else Icons.Default.Error
val baseMessage = if (isValidEmail) "Looks great!" else "Oops! Check your email."
val maxChars = 300
val adjustedMessage = buildString {
repeat(sliderValue.toInt()) {
append(baseMessage)
}
}.take(maxChars)
Column(
modifier
.fillMaxSize()
.background(Color(0xFF121212))
.padding(16.dp)
) {
Text(
"What's your email?",
fontSize = 20.sp,
color = Color.White,
fontWeight = FontWeight.Bold
)
Spacer(Modifier.height(12.dp))
TextField(
value = email,
onValueChange = { email = it },
placeholder = { Text("[email protected]") },
modifier = Modifier
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(12.dp))
)
Spacer(Modifier.height(16.dp))
Text(
text = "Message intensity: ${sliderValue.toInt()}x",
color = Color.White
)
Slider(
value = sliderValue,
onValueChange = { sliderValue = it },
valueRange = 1f..10f,
steps = 3,
modifier = Modifier.fillMaxWidth()
)
Box(
modifier = Modifier
.fillMaxSize()
.layout { measurable, constraints ->
layout(constraints.maxWidth, constraints.maxHeight) {
val sliderInfluence = sliderValue
val placeable = measurable.measure(constraints)
val ime = WindowInsetsRulers.Ime
val animation = ime.getAnimation(this)
val height = constraints.maxHeight.toFloat()
val sourceBottom = animation.source.bottom.current(height)
val targetBottom = animation.target.bottom.current(height)
val targetY = if (!animation.isVisible || sourceBottom < targetBottom) {
0f
} else if (animation.isAnimating) {
targetBottom - placeable.height
} else {
ime.current.bottom.current(height) - placeable.height
}
if (targetY != verticalOffset.targetValue) {
coroutineScope.launch {
verticalOffset.animateTo(targetY)
}
}
placeable.place(0, verticalOffset.value.roundToInt())
}
},
contentAlignment = Alignment.BottomCenter
) {
[email protected](
visible = showCard,
enter = fadeIn(animationSpec = tween(400)) + scaleIn(),
exit = fadeOut(animationSpec = tween(300)) + scaleOut()
) {
Box(
Modifier
.graphicsLayer {
shadowElevation = 16f
shape = RoundedCornerShape(24.dp)
clip = true
}
.background(backgroundColor)
.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = icon,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(24.dp)
)
Spacer(Modifier.width(12.dp))
Text(
text = adjustedMessage,
color = Color.White,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold
)
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment