androidkotlinandroid-jetpack-composeandroid-canvas

Animated bottle water size error in jetpack compose


This code calculates the amount of water a person should drink daily. and as you click the button, it reduces the water from the full bottle.

Actually, everything in the program works clearly, but on the first click, the water level does not change as a result of the click. After the first click everything works normally. I can't overcome this, can you help me?

 package com.example.diettrackingapp.home

import android.widget.Toast
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.diettrackingapp.firebase.PatientViewModel

@Composable
fun WaterScreen() {
    val context = LocalContext.current
    var totalWaterAmount by remember { mutableStateOf(0) }
    var usedWaterAmount by remember { mutableStateOf(0) }


    val weight = 70

    totalWaterAmount = (weight * 35)


    val configuration = LocalConfiguration.current
    val screenWidth = configuration.screenWidthDp.dp

    Column(
        modifier = Modifier
            .width(screenWidth * 0.4f),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Top
    ) {
        Spacer(modifier = Modifier.height(25.dp))
        Column(
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            WaterBottle(
                modifier = Modifier
                    .width(screenWidth * 0.3f)
                    .height(screenWidth * 0.6f),
                totalWaterAmount = totalWaterAmount,
                unit = "ml",
                usedWaterAmount = usedWaterAmount
            )
            Spacer(modifier = Modifier.height(10.dp))
            Text(text = "Total:$totalWaterAmount ml")
            Button(onClick = {
                if (usedWaterAmount < totalWaterAmount) usedWaterAmount += 200 else Toast.makeText(
                    context, "Bottle is Empty", Toast.LENGTH_LONG
                ).show()
            }) {
                Text(text = "Drink")
            }
        }
    }
}

@Composable
fun WaterBottle(
    modifier: Modifier,
    totalWaterAmount: Int,
    unit: String,
    usedWaterAmount: Int,
    waterColor: Color = Color(0xFF1F97F8),
    bottleColor: Color = Color.White,
    capColor: Color = Color(0xFF01355F)
) {
    val waterPercentage by animateFloatAsState(
        targetValue = if (totalWaterAmount > 0) usedWaterAmount.toFloat() / totalWaterAmount.toFloat() else 0f,
        animationSpec = tween(1000), label = ""
    )

    val usedWaterAnimation by animateIntAsState(
        targetValue = usedWaterAmount, animationSpec = tween(1000), label = ""
    )

    Box(
        modifier = modifier
            .clip(RoundedCornerShape(10.dp))
            .background(Color(0xFFFDF7BA))
            .padding(10.dp)
    ) {
        Canvas(modifier = Modifier.fillMaxSize()) {
            val width = size.width
            val height = size.height

            val capWidth = size.width * 0.55f
            val capHeight = size.height * 0.08f

            val bottleBodyPath = Path().apply {
                moveTo(width * 0.3f, height * 0.1f)
                lineTo(
                    width * 0.3f, height * 0.2f
                )
                quadraticBezierTo(
                    0f, height * 0.3f, 0f, height * 0.4f
                )
                lineTo(
                    0f, height * 0.95f
                )
                quadraticBezierTo(
                    0f, height, width * 0.05f, height
                )
                lineTo(
                    width * 0.95f, height
                )
                quadraticBezierTo(
                    width, height, width, height * 0.95f
                )
                lineTo(
                    width, height * 0.4f
                )
                quadraticBezierTo(
                    width, height * 0.3f, width * 0.7f, height * 0.2f
                )
                lineTo(
                    width * 0.7f, height * 0.1f
                )
                close()
            }
            clipPath(
                bottleBodyPath
            ) {
                drawRect(
                    color = bottleColor, size = size
                )
                val waterWavesYPosition = waterPercentage * size.height
                val waterPath = Path().apply {
                    moveTo(0f, waterWavesYPosition)
                    lineTo(
                        size.width, waterWavesYPosition
                    )
                    lineTo(
                        size.width, size.height
                    )
                    lineTo(
                        0f, size.height
                    )
                    close()
                }
                drawPath(
                    waterPath, waterColor
                )
            }
            drawRoundRect(
                color = capColor,
                size = Size(capWidth, capHeight),
                topLeft = Offset(size.width / 2 - capWidth / 2f, 0f),
                cornerRadius = CornerRadius(45f, 45f)
            )
        }

        val fontSize = with(LocalConfiguration.current) { screenWidthDp * 0.03 }.sp

        val text = buildAnnotatedString {
            withStyle(
                SpanStyle(
                    color = Color.Black,
                    fontSize = fontSize
                )
            ) {
                append("Drunk Water:\n")
            }
            withStyle(
                style = SpanStyle(
                    color = Color.Black,
                    fontSize = fontSize * 2
                )
            ) {
                append(usedWaterAnimation.toString())
            }
            withStyle(
                style = SpanStyle(
                    color = Color.Black,
                    fontSize = fontSize
                )
            ) {
                append(" ")
                append(unit)
            }
        }
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text(text = text)
        }
    }
}

 this is the bottle


Solution

  • Adjusts the bottle path and water drawing logic using capHeightRatio

    @Composable
    fun WaterScreen() {
        val context = LocalContext.current
        var usedWaterAmount by remember { mutableStateOf(0) }
    
        val weight = 70
        val totalWaterAmount = remember { weight * 35 }
    
        val configuration = LocalConfiguration.current
        val screenWidth = configuration.screenWidthDp.dp
    
        Column(
            modifier = Modifier
                .width(screenWidth * 0.4f),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Top
        ) {
            Spacer(modifier = Modifier.height(25.dp))
            Column(
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                WaterBottle(
                    modifier = Modifier
                        .width(screenWidth * 0.3f)
                        .height(screenWidth * 0.6f),
                    totalWaterAmount = totalWaterAmount,
                    unit = "ml",
                    usedWaterAmount = usedWaterAmount
                )
                Spacer(modifier = Modifier.height(10.dp))
                Text(text = "Total: $totalWaterAmount ml")
                Button(onClick = {
                    if (usedWaterAmount < totalWaterAmount) usedWaterAmount += 200 else Toast.makeText(
                        context, "Bottle is Empty", Toast.LENGTH_LONG
                    ).show()
                }) {
                    Text(text = "Drink")
                }
            }
        }
    }
    
    @Composable
    fun WaterBottle(
        modifier: Modifier,
        totalWaterAmount: Int,
        unit: String,
        usedWaterAmount: Int,
        waterColor: Color = Color(0xFF1F97F8),
        bottleColor: Color = Color.White,
        capColor: Color = Color(0xFF01355F)
    ) {
        val waterPercentage by animateFloatAsState(
            targetValue = if (totalWaterAmount > 0) usedWaterAmount.toFloat() / totalWaterAmount.toFloat() else 0f,
            animationSpec = tween(1000), label = ""
        )
    
        val usedWaterAnimation by animateIntAsState(
            targetValue = usedWaterAmount, animationSpec = tween(1000), label = ""
        )
    
        Box(
            modifier = modifier
                .clip(RoundedCornerShape(10.dp))
                .background(Color(0xFFFDF7BA))
                .padding(10.dp)
        ) {
            Canvas(modifier = Modifier.fillMaxSize()) {
                val width = size.width
                val height = size.height
    
                // Adjust the starting position below the cap
                val capHeightRatio = 0.1f // Adjust this value based on your cap height
                val bodyStartY = height * capHeightRatio
    
                // Draw bottle body
                val bottleBodyPath = Path().apply {
                    moveTo(width * 0.3f, bodyStartY)
                    lineTo(width * 0.3f, bodyStartY + height * 0.1f)
                    quadraticBezierTo(0f, bodyStartY + height * 0.2f, 0f, bodyStartY + height * 0.3f)
                    lineTo(0f, height * 0.95f)
                    quadraticBezierTo(0f, height, width * 0.05f, height)
                    lineTo(width * 0.95f, height)
                    quadraticBezierTo(width, height, width, height * 0.95f)
                    lineTo(width, bodyStartY + height * 0.3f)
                    quadraticBezierTo(width, bodyStartY + height * 0.2f, width * 0.7f, bodyStartY + height * 0.1f)
                    lineTo(width * 0.7f, bodyStartY)
                    close()
                }
                clipPath(bottleBodyPath) {
                    drawRect(color = bottleColor, size = size)
    
                    val waterWavesYPosition = bodyStartY + waterPercentage * (height - bodyStartY)
                    val waterPath = Path().apply {
                        moveTo(0f, waterWavesYPosition)
                        lineTo(size.width, waterWavesYPosition)
                        lineTo(size.width, size.height)
                        lineTo(0f, size.height)
                        close()
                    }
                    drawPath(waterPath, waterColor)
                }
    
                // Draw cap outside the clipping
                val capWidth = size.width * 0.55f
                val capHeight = height * capHeightRatio
                drawRoundRect(
                    color = capColor,
                    size = Size(capWidth, capHeight),
                    topLeft = Offset(size.width / 2 - capWidth / 2f, 0f),
                    cornerRadius = CornerRadius(45f, 45f)
                )
            }
    
            val fontSize = with(LocalConfiguration.current) { screenWidthDp * 0.03 }.sp
            val text = buildAnnotatedString {
                withStyle(SpanStyle(color = Color.Black, fontSize = fontSize)) {
                    append("Drunk Water:\n")
                }
                withStyle(SpanStyle(color = Color.Black, fontSize = fontSize * 2)) {
                    append(usedWaterAnimation.toString())
                }
                withStyle(SpanStyle(color = Color.Black, fontSize = fontSize)) {
                    append(" ")
                    append(unit)
                }
            }
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Text(text = text)
            }
        }
    }
    

    enter image description here