Created
March 5, 2026 08:58
-
-
Save TuleSimon/9fdef920e1496d788340b720bd4b291b to your computer and use it in GitHub Desktop.
Animate Review Compose Implementation - Inspiration from https://www.pinterest.com/pin/1137088605932327547/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.anonymous.animatedreview | |
| import android.os.Bundle | |
| import androidx.activity.ComponentActivity | |
| import androidx.activity.compose.setContent | |
| import androidx.activity.enableEdgeToEdge | |
| import androidx.annotation.DrawableRes | |
| import androidx.compose.animation.AnimatedContent | |
| import androidx.compose.animation.AnimatedVisibility | |
| import androidx.compose.animation.core.Animatable | |
| import androidx.compose.animation.core.FastOutSlowInEasing | |
| import androidx.compose.animation.core.Spring | |
| import androidx.compose.animation.core.spring | |
| import androidx.compose.animation.core.tween | |
| import androidx.compose.animation.expandVertically | |
| import androidx.compose.animation.fadeIn | |
| import androidx.compose.animation.fadeOut | |
| import androidx.compose.animation.shrinkVertically | |
| import androidx.compose.animation.slideInHorizontally | |
| import androidx.compose.animation.slideInVertically | |
| import androidx.compose.animation.slideOutHorizontally | |
| import androidx.compose.animation.slideOutVertically | |
| import androidx.compose.animation.togetherWith | |
| import androidx.compose.foundation.Canvas | |
| import androidx.compose.foundation.background | |
| import androidx.compose.foundation.border | |
| import androidx.compose.foundation.clickable | |
| import androidx.compose.foundation.interaction.MutableInteractionSource | |
| import androidx.compose.foundation.interaction.collectIsDraggedAsState | |
| 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.aspectRatio | |
| 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.layout.statusBarsPadding | |
| import androidx.compose.foundation.shape.CircleShape | |
| import androidx.compose.foundation.shape.RoundedCornerShape | |
| import androidx.compose.foundation.text.BasicTextField | |
| import androidx.compose.material3.ExperimentalMaterial3Api | |
| import androidx.compose.material3.Icon | |
| import androidx.compose.material3.MaterialTheme | |
| import androidx.compose.material3.Slider | |
| import androidx.compose.material3.SliderDefaults | |
| import androidx.compose.material3.SliderState | |
| import androidx.compose.material3.Text | |
| import androidx.compose.runtime.Composable | |
| import androidx.compose.runtime.LaunchedEffect | |
| import androidx.compose.runtime.getValue | |
| import androidx.compose.runtime.mutableFloatStateOf | |
| 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.draw.drawBehind | |
| import androidx.compose.ui.draw.scale | |
| import androidx.compose.ui.focus.FocusRequester | |
| import androidx.compose.ui.focus.focusRequester | |
| import androidx.compose.ui.focus.onFocusChanged | |
| import androidx.compose.ui.geometry.CornerRadius | |
| import androidx.compose.ui.geometry.Offset | |
| import androidx.compose.ui.geometry.Size | |
| import androidx.compose.ui.graphics.Brush | |
| import androidx.compose.ui.graphics.Color | |
| import androidx.compose.ui.graphics.Path | |
| import androidx.compose.ui.graphics.StrokeCap | |
| import androidx.compose.ui.graphics.drawscope.Stroke | |
| import androidx.compose.ui.graphics.drawscope.withTransform | |
| import androidx.compose.ui.res.painterResource | |
| import androidx.compose.ui.text.font.FontWeight | |
| import androidx.compose.ui.text.style.TextAlign | |
| import androidx.compose.ui.tooling.preview.Preview | |
| import androidx.compose.ui.unit.dp | |
| import androidx.compose.ui.unit.sp | |
| import androidx.compose.ui.util.lerp | |
| import com.anonymous.animatedreview.ui.theme.AnimatedReviewTheme | |
| import kotlinx.coroutines.launch | |
| import androidx.compose.ui.graphics.lerp as lerpColor | |
| private const val TRANSITION_MS = 600 | |
| private val BgGreen = Color(0xFFB2E060) | |
| private val BgYellow = Color(0xFFF0CA43) | |
| private val BgRed = Color(0xFFE86F6F) | |
| private val AccentGreen = Color(0xFF4b7000) | |
| private val AccentYellow = Color(0xFF6B5922) | |
| private val AccentRed = Color(0xFF663130) | |
| enum class Mood { HAPPY, MEDIUM, SAD } | |
| private fun Float.toMood(): Mood = when { | |
| this > 0.67f -> Mood.HAPPY | |
| this > 0.33f -> Mood.MEDIUM | |
| else -> Mood.SAD | |
| } | |
| private fun Mood.label(): String = when (this) { | |
| Mood.HAPPY -> "GOOD" | |
| Mood.MEDIUM -> "NOT BAD" | |
| Mood.SAD -> "BAD" | |
| } | |
| /** | |
| * Interpolates a float across three keyframes (bad → medium → good) | |
| * based on [p] in the range 0f..1f. | |
| */ | |
| private fun triLerp(bad: Float, medium: Float, good: Float, p: Float): Float = | |
| if (p <= 0.5f) lerp(bad, medium, p * 2f) else lerp(medium, good, (p - 0.5f) * 2f) | |
| /** | |
| * Interpolates a [Color] across three keyframes (bad → medium → good) | |
| * based on [p] in the range 0f..1f. | |
| */ | |
| private fun triLerpColor(bad: Color, medium: Color, good: Color, p: Float): Color = | |
| if (p <= 0.5f) lerpColor(bad, medium, p * 2f) else lerpColor(medium, good, (p - 0.5f) * 2f) | |
| private fun backgroundFor(p: Float) = triLerpColor(BgRed, BgYellow, BgGreen, p) | |
| private fun accentFor(p: Float) = triLerpColor(AccentRed, AccentYellow, AccentGreen, p) | |
| /** | |
| * Snaps [p] to the nearest of the three mood positions: 0f (bad), 0.5f (not bad), 1f (good). | |
| */ | |
| private fun snapToMood(p: Float): Float = when { | |
| p < 0.25f -> 0f | |
| p < 0.75f -> 0.5f | |
| else -> 1f | |
| } | |
| class MainActivity : ComponentActivity() { | |
| override fun onCreate(savedInstanceState: Bundle?) { | |
| super.onCreate(savedInstanceState) | |
| enableEdgeToEdge() | |
| setContent { | |
| AnimatedReviewTheme { | |
| ReviewScreen() | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Draws the animated smiley face. No internal animation — all motion is driven externally by [progress]. | |
| * | |
| * @param progress 0f = bad/sad, 0.5f = neutral, 1f = good/happy. | |
| * @param modifier Applied to the underlying [Canvas]. | |
| */ | |
| @Composable | |
| fun SmileyFaceCanvas( | |
| progress: Float, | |
| modifier: Modifier = Modifier | |
| ) { | |
| val accent = accentFor(progress) | |
| Canvas(modifier = modifier) { | |
| val w = size.width | |
| val h = size.height | |
| val eyeHalfWidth = w * triLerp(bad = 0.14f, medium = 0.17f, good = 0.20f, p = progress) | |
| val eyeHalfHeight = w * triLerp(bad = 0.14f, medium = 0.057f, good = 0.20f, p = progress) | |
| val eyeY = h * triLerp(bad = 0.38f, medium = 0.37f, good = 0.35f, p = progress) | |
| val leftEyeX = w * triLerp(bad = 0.30f, medium = 0.27f, good = 0.28f, p = progress) | |
| val rightEyeX = w * triLerp(bad = 0.70f, medium = 0.73f, good = 0.72f, p = progress) | |
| val eyeRotation = triLerp(bad = 90f, medium = 0f, good = 0f, p = progress) | |
| withTransform({ rotate(eyeRotation, Offset(leftEyeX, eyeY)) }) { | |
| drawRoundRect( | |
| color = accent, | |
| topLeft = Offset(leftEyeX - eyeHalfWidth, eyeY - eyeHalfHeight), | |
| size = Size(eyeHalfWidth * 2f, eyeHalfHeight * 2f), | |
| cornerRadius = CornerRadius(eyeHalfHeight, eyeHalfHeight) | |
| ) | |
| } | |
| withTransform({ rotate(-eyeRotation, Offset(rightEyeX, eyeY)) }) { | |
| drawRoundRect( | |
| color = accent, | |
| topLeft = Offset(rightEyeX - eyeHalfWidth, eyeY - eyeHalfHeight), | |
| size = Size(eyeHalfWidth * 2f, eyeHalfHeight * 2f), | |
| cornerRadius = CornerRadius(eyeHalfHeight, eyeHalfHeight) | |
| ) | |
| } | |
| val mouthCenterX = w * 0.50f | |
| val mouthY = h * 0.60f | |
| val mouthHalfWidth = w * 0.1f | |
| val mouthCurveDepth = h * 0.11f | |
| val mouthRotation = triLerp(bad = 180f, medium = 180f, good = 0f, p = progress) | |
| withTransform({ rotate(mouthRotation, Offset(mouthCenterX, mouthY)) }) { | |
| drawPath( | |
| path = Path().apply { | |
| moveTo(mouthCenterX - mouthHalfWidth, mouthY) | |
| quadraticTo(mouthCenterX, mouthY + mouthCurveDepth, mouthCenterX + mouthHalfWidth, mouthY) | |
| }, | |
| color = accent, | |
| style = Stroke(width = 16.dp.toPx(), cap = StrokeCap.Round) | |
| ) | |
| } | |
| } | |
| } | |
| /** | |
| * The slider thumb — a small circle with a mood-matching mouth inside. | |
| * | |
| * @param progress Same 0f–1f scale as [SmileyFaceCanvas]. | |
| */ | |
| @Composable | |
| private fun SliderThumb(progress: Float) { | |
| val accent = accentFor(progress) | |
| val lipColor = backgroundFor(progress) | |
| Canvas(modifier = Modifier.size(44.dp)) { | |
| val radius = size.minDimension / 2f | |
| val cx = size.width / 2f | |
| val cy = size.height / 2f | |
| drawCircle(color = accent, radius = radius) | |
| val mouthHalfWidth = radius * 0.38f | |
| val mouthCurveDepth = radius * 0.52f | |
| val mouthY = cy + radius * 0.10f | |
| val mouthRotation = triLerp(bad = 180f, medium = 180f, good = 0f, p = progress) | |
| withTransform({ rotate(mouthRotation, Offset(cx, mouthY)) }) { | |
| drawPath( | |
| path = Path().apply { | |
| moveTo(cx - mouthHalfWidth, mouthY) | |
| quadraticTo(cx, mouthY + mouthCurveDepth, cx + mouthHalfWidth, mouthY) | |
| }, | |
| color = lipColor, | |
| style = Stroke(width = 4.dp.toPx(), cap = StrokeCap.Round) | |
| ) | |
| } | |
| } | |
| } | |
| /** | |
| * Custom slider track — a semi-transparent line with circles at each snap position. | |
| */ | |
| @OptIn(ExperimentalMaterial3Api::class) | |
| @Composable | |
| private fun SliderTrack(@Suppress("UNUSED_PARAMETER") state: SliderState) { | |
| val trackColor = Color.Black.copy(alpha = 0.18f) | |
| Canvas( | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .height(44.dp) | |
| ) { | |
| val centerY = size.height / 2f | |
| val dotRadius = 7.dp.toPx() | |
| drawLine( | |
| color = trackColor, | |
| start = Offset(0f, centerY), | |
| end = Offset(size.width, centerY), | |
| strokeWidth = 4.dp.toPx(), | |
| cap = StrokeCap.Round | |
| ) | |
| listOf(0f, 0.5f, 1f).forEach { position -> | |
| drawCircle( | |
| color = trackColor, | |
| radius = dotRadius, | |
| center = Offset(size.width * position, centerY) | |
| ) | |
| } | |
| } | |
| } | |
| /** | |
| * The main review screen. Shows an animated smiley face driven by a three-position slider. | |
| * Tapping "Add note" hides the slider and shows a text input. | |
| */ | |
| @OptIn(ExperimentalMaterial3Api::class) | |
| @Composable | |
| fun ReviewScreen() { | |
| val scope = rememberCoroutineScope() | |
| val progress = remember { Animatable(1f) } | |
| val interactionSource = remember { MutableInteractionSource() } | |
| val isSliderDragged by interactionSource.collectIsDraggedAsState() | |
| var lastDragPosition by remember { mutableFloatStateOf(1f) } | |
| var showNote by remember { mutableStateOf(false) } | |
| val bg = backgroundFor(progress.value) | |
| val bgLight = lerpColor(bg, Color.White, 0.30f) | |
| val accent = accentFor(progress.value) | |
| val mood = progress.value.toMood() | |
| fun goTo(target: Float) { | |
| scope.launch { | |
| progress.animateTo(target, tween(TRANSITION_MS, easing = FastOutSlowInEasing)) | |
| } | |
| } | |
| Box( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .drawBehind { | |
| drawRect( | |
| brush = Brush.radialGradient( | |
| colors = listOf(bgLight, bg), | |
| center = Offset(size.width / 2f, size.height * 0.35f), | |
| radius = size.width * 0.85f | |
| ) | |
| ) | |
| }, | |
| contentAlignment = Alignment.Center | |
| ) { | |
| Column( | |
| modifier = Modifier | |
| .statusBarsPadding() | |
| .fillMaxSize() | |
| .padding(horizontal = 32.dp), | |
| horizontalAlignment = Alignment.CenterHorizontally | |
| ) { | |
| Row( | |
| modifier = Modifier.fillMaxWidth(), | |
| horizontalArrangement = Arrangement.SpaceBetween | |
| ) { | |
| IconButton(R.drawable.baseline_close_24) { showNote = false } | |
| IconButton(R.drawable.outline_info_24) | |
| } | |
| AnimatedVisibility( | |
| visible = !showNote, | |
| enter = fadeIn() + expandVertically(), | |
| exit = fadeOut() + shrinkVertically() | |
| ) { | |
| Column(horizontalAlignment = Alignment.CenterHorizontally) { | |
| Spacer(Modifier.height(48.dp)) | |
| Text( | |
| text = "How was your shopping \nexperience?", | |
| color = accent, | |
| fontWeight = FontWeight.Bold, | |
| fontSize = 24.sp, | |
| lineHeight = 28.sp, | |
| textAlign = TextAlign.Center | |
| ) | |
| } | |
| } | |
| Spacer(Modifier.height(24.dp)) | |
| SmileyFaceCanvas( | |
| progress = progress.value, | |
| modifier = Modifier | |
| .fillMaxWidth(0.75f) | |
| .aspectRatio(1f) | |
| ) | |
| Spacer(Modifier.height(16.dp)) | |
| if (!showNote) { | |
| Column(horizontalAlignment = Alignment.CenterHorizontally) { | |
| AnimatedContent( | |
| mood.label(), | |
| modifier = Modifier.fillMaxWidth(), | |
| transitionSpec = { | |
| slideInHorizontally { -it } + fadeIn() togetherWith | |
| slideOutHorizontally { -it } + fadeOut() | |
| }, | |
| contentAlignment = Alignment.Center | |
| ) { | |
| Text( | |
| text = it, | |
| color = accent.copy(alpha = 0.45f), | |
| fontWeight = FontWeight.Black, | |
| textAlign = TextAlign.Center, | |
| fontSize = 52.sp, | |
| letterSpacing = -5.sp | |
| ) | |
| } | |
| Spacer(Modifier.height(32.dp)) | |
| Slider( | |
| value = progress.value, | |
| onValueChange = { v -> | |
| lastDragPosition = v | |
| if (isSliderDragged) { | |
| scope.launch { progress.snapTo(v) } | |
| } | |
| }, | |
| onValueChangeFinished = { | |
| goTo(snapToMood(lastDragPosition)) | |
| }, | |
| interactionSource = interactionSource, | |
| valueRange = 0f..1f, | |
| colors = SliderDefaults.colors( | |
| activeTrackColor = bg, | |
| inactiveTrackColor = bg, | |
| activeTickColor = Color.Transparent, | |
| inactiveTickColor = Color.Transparent | |
| ), | |
| thumb = { SliderThumb(progress = progress.value) }, | |
| track = { SliderTrack(it) }, | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .scale(scaleX = 1.08f, scaleY = 1f) | |
| ) | |
| Row( | |
| modifier = Modifier.fillMaxWidth(), | |
| horizontalArrangement = Arrangement.SpaceBetween | |
| ) { | |
| Text( | |
| "Bad", | |
| color = accent, | |
| fontWeight = if (mood == Mood.SAD) FontWeight.Bold else FontWeight.Normal, | |
| modifier = Modifier.clickable { goTo(0f) } | |
| ) | |
| Text( | |
| "Not Bad", | |
| color = accent.copy(alpha = if (mood == Mood.MEDIUM) 1f else 0.6f), | |
| fontWeight = if (mood == Mood.MEDIUM) FontWeight.Bold else FontWeight.Normal, | |
| modifier = Modifier.clickable { goTo(0.5f) } | |
| ) | |
| Text( | |
| "Good", | |
| color = accent.copy(alpha = if (mood == Mood.HAPPY) 1f else 0.6f), | |
| fontWeight = if (mood == Mood.HAPPY) FontWeight.Bold else FontWeight.Normal, | |
| modifier = Modifier.clickable { goTo(1f) } | |
| ) | |
| } | |
| } | |
| } | |
| AnimatedVisibility( | |
| visible = showNote, | |
| enter = fadeIn() + slideInVertically( | |
| animationSpec = spring( | |
| dampingRatio = Spring.DampingRatioMediumBouncy, | |
| stiffness = Spring.StiffnessLow | |
| ) | |
| ), | |
| exit = fadeOut() + slideOutVertically { it * 6 } | |
| ) { | |
| NoteField(accent = accent) | |
| } | |
| Spacer(Modifier.weight(1f)) | |
| AnimatedVisibility( | |
| visible = !showNote, | |
| enter = fadeIn() + slideInVertically( | |
| animationSpec = spring( | |
| dampingRatio = Spring.DampingRatioMediumBouncy, | |
| stiffness = Spring.StiffnessLow | |
| ) | |
| ), | |
| exit = fadeOut() + slideOutVertically { -it * 7 } | |
| ) { | |
| SubmitFooter(accent = accent, onAddNote = { showNote = true }) | |
| } | |
| Spacer( | |
| Modifier | |
| .height(40.dp) | |
| .statusBarsPadding() | |
| ) | |
| } | |
| } | |
| } | |
| /** | |
| * Expandable text input shown when the user taps "Add note". | |
| * Auto-focuses on appearance. Border becomes visible when focused. | |
| * | |
| * @param accent Color used for the border, text, and submit button. | |
| */ | |
| @Composable | |
| private fun NoteField(accent: Color) { | |
| var note by remember { mutableStateOf("") } | |
| var focused by remember { mutableStateOf(false) } | |
| val focusRequester = remember { FocusRequester() } | |
| LaunchedEffect(Unit) { focusRequester.requestFocus() } | |
| Box( | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .then( | |
| if (focused) Modifier.border(2.dp, accent, RoundedCornerShape(24.dp)) | |
| else Modifier | |
| ) | |
| .background(Color.Black.copy(alpha = 0.1f), RoundedCornerShape(24.dp)) | |
| .padding(20.dp) | |
| ) { | |
| BasicTextField( | |
| value = note, | |
| onValueChange = { note = it }, | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .height(110.dp) | |
| .align(Alignment.TopStart) | |
| .focusRequester(focusRequester) | |
| .onFocusChanged { focused = it.isFocused }, | |
| textStyle = MaterialTheme.typography.bodyMedium.copy(color = accent), | |
| decorationBox = { inner -> | |
| if (note.isEmpty()) { | |
| Text( | |
| "Add note", | |
| style = MaterialTheme.typography.bodyMedium.copy( | |
| color = accent.copy(alpha = 0.45f) | |
| ) | |
| ) | |
| } | |
| inner() | |
| } | |
| ) | |
| Row( | |
| modifier = Modifier | |
| .align(Alignment.BottomEnd) | |
| .background(accent, RoundedCornerShape(100f)) | |
| .padding(horizontal = 14.dp, vertical = 12.dp), | |
| horizontalArrangement = Arrangement.spacedBy(6.dp), | |
| verticalAlignment = Alignment.CenterVertically | |
| ) { | |
| Text( | |
| "Submit", | |
| style = MaterialTheme.typography.bodyMedium.copy( | |
| color = Color.White, | |
| fontWeight = FontWeight.SemiBold | |
| ) | |
| ) | |
| Icon( | |
| painterResource(R.drawable.outline_arrow_forward_24), | |
| tint = Color.White, | |
| modifier = Modifier.size(15.dp), | |
| contentDescription = null | |
| ) | |
| } | |
| } | |
| } | |
| /** | |
| * A simple icon button with a semi-transparent circular background. | |
| * | |
| * @param icon Drawable resource to display. | |
| * @param onClick Called when the button is tapped. | |
| */ | |
| @Composable | |
| private fun IconButton(@DrawableRes icon: Int, onClick: () -> Unit = {}) { | |
| Box( | |
| Modifier | |
| .clickable { onClick() } | |
| .background(Color.Black.copy(0.1f), CircleShape) | |
| .padding(12.dp) | |
| ) { | |
| Icon( | |
| painterResource(icon), | |
| contentDescription = null, | |
| tint = Color.Black, | |
| modifier = Modifier.size(24.dp) | |
| ) | |
| } | |
| } | |
| /** | |
| * Bottom bar with an "Add note" button and a submit action. | |
| * | |
| * @param accent Color applied to text and the submit pill. | |
| * @param onAddNote Called when the user taps "Add note". | |
| */ | |
| @Composable | |
| private fun SubmitFooter(accent: Color, onAddNote: () -> Unit) { | |
| Row( | |
| Modifier | |
| .fillMaxWidth(0.9f) | |
| .background(Color.Black.copy(0.1f), RoundedCornerShape(100f)), | |
| verticalAlignment = Alignment.CenterVertically | |
| ) { | |
| Text( | |
| "Add note", | |
| style = MaterialTheme.typography.bodyMedium.copy( | |
| color = accent, | |
| fontWeight = FontWeight.SemiBold | |
| ), | |
| modifier = Modifier | |
| .padding(horizontal = 16.dp) | |
| .weight(1f) | |
| .clickable { onAddNote() } | |
| ) | |
| Row( | |
| Modifier | |
| .background(accent, RoundedCornerShape(100f)) | |
| .padding(horizontal = 14.dp, vertical = 15.dp), | |
| horizontalArrangement = Arrangement.spacedBy(5.dp), | |
| verticalAlignment = Alignment.CenterVertically | |
| ) { | |
| Text( | |
| "Submit", | |
| style = MaterialTheme.typography.bodyMedium.copy( | |
| color = Color.White, | |
| fontWeight = FontWeight.SemiBold | |
| ) | |
| ) | |
| Icon( | |
| painterResource(R.drawable.outline_arrow_forward_24), | |
| tint = Color.White, | |
| modifier = Modifier.size(15.dp), | |
| contentDescription = null | |
| ) | |
| } | |
| } | |
| } | |
| @Preview(showBackground = true, backgroundColor = 0xFFB2E060) | |
| @Composable | |
| private fun PreviewHappy() { | |
| AnimatedReviewTheme { | |
| SmileyFaceCanvas(progress = 1f, modifier = Modifier.size(280.dp)) | |
| } | |
| } | |
| @Preview(showBackground = true, backgroundColor = 0xFFF0CA43) | |
| @Composable | |
| private fun PreviewMedium() { | |
| AnimatedReviewTheme { | |
| SmileyFaceCanvas(progress = 0.5f, modifier = Modifier.size(280.dp)) | |
| } | |
| } | |
| @Preview(showBackground = true, backgroundColor = 0xFFE86F6F) | |
| @Composable | |
| private fun PreviewSad() { | |
| AnimatedReviewTheme { | |
| SmileyFaceCanvas(progress = 0f, modifier = Modifier.size(280.dp)) | |
| } | |
| } | |
| @Preview(showSystemUi = true) | |
| @Composable | |
| private fun PreviewReviewScreen() { | |
| AnimatedReviewTheme { | |
| ReviewScreen() | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment