Water Bottle -Tugas pertemuan 10

 

Pemrograman Perangkat Beregerak - Tugas Pertemuan 10


Nama: Muhammad Rafi Budi Purnama

NRP: 5025221307

Kelas: PPB - A

water bottle




Main.kt
package com.example.waterbottle

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.example.waterbottle.ui.theme.WaterBottleTheme

// Constants at top level
private const val DEFAULT_WATER_AMOUNT = 100
private const val TOTAL_DAILY_WATER = 2500
private const val DRINK_INCREMENT = 200

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
WaterBottleTheme {
WaterBottleScreen()
}
}
}
}

@Composable
private fun WaterBottleScreen() {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
WaterBottleContent()
}
}

@Composable
private fun WaterBottleContent() {
var currentWaterAmount by remember { mutableStateOf(DEFAULT_WATER_AMOUNT) }
val dailyTarget by remember { mutableStateOf(TOTAL_DAILY_WATER) }

Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {

WatterBottle(
totalWaterAmount = dailyTarget,
unit = "ml",
usedWaterAmount = currentWaterAmount
)

Spacer(modifier = Modifier.height(20.dp))

WaterAmountDisplay(totalAmount = dailyTarget)

DrinkButton {
currentWaterAmount += DRINK_INCREMENT
}
}
}

@Composable
private fun WaterAmountDisplay(totalAmount: Int) {
Text(
text = "Total Amount is : $totalAmount",
textAlign = TextAlign.Center
)
}

@Composable
private fun DrinkButton(onDrinkClick: () -> Unit) {
Button(
onClick = onDrinkClick,
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xffff007f)
)
) {
Text(text = "Drink")
}
}

WaterBottle.kt
package com.example.waterbottle

import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.text.*
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*

// Default colors as constants
private object BottleColors {
val DefaultWaterColor = Color(0xff279EFF)
val DefaultBottleColor = Color.White
val DefaultCapColor = Color(0xFF0065B9)
}

// Bottle dimensions
private object BottleDimensions {
val Width = 200.dp
val Height = 600.dp
}

// Animation configuration
private object AnimationConfig {
const val DURATION_MS = 1000
val ANIMATION_SPEC = tween<Float>(durationMillis = DURATION_MS)
val INT_ANIMATION_SPEC = tween<Int>(durationMillis = DURATION_MS)
}

@Composable
fun WatterBottle(
modifier: Modifier = Modifier,
totalWaterAmount: Int,
unit: String,
usedWaterAmount: Int,
waterWavesColor: Color = BottleColors.DefaultWaterColor,
bottleColor: Color = BottleColors.DefaultBottleColor,
capColor: Color = BottleColors.DefaultCapColor
) {
val animatedWaterPercentage = rememberWaterPercentageAnimation(
usedAmount = usedWaterAmount,
totalAmount = totalWaterAmount
)

val animatedUsedAmount = rememberUsedAmountAnimation(usedWaterAmount)

Box(
modifier = modifier
.width(BottleDimensions.Width)
.height(BottleDimensions.Height)
) {
BottleCanvas(
waterPercentage = animatedWaterPercentage,
waterColor = waterWavesColor,
bottleColor = bottleColor,
capColor = capColor
)

WaterAmountText(
usedAmount = animatedUsedAmount,
unit = unit,
waterPercentage = animatedWaterPercentage,
textColor = if (animatedWaterPercentage > 0.5f) bottleColor else waterWavesColor
)
}
}

@Composable
private fun rememberWaterPercentageAnimation(
usedAmount: Int,
totalAmount: Int
): Float {
return animateFloatAsState(
targetValue = usedAmount.toFloat() / totalAmount.toFloat(),
label = "Water Waves animation",
animationSpec = AnimationConfig.ANIMATION_SPEC
).value
}

@Composable
private fun rememberUsedAmountAnimation(usedAmount: Int): Int {
return animateIntAsState(
targetValue = usedAmount,
label = "Used water amount animation",
animationSpec = AnimationConfig.INT_ANIMATION_SPEC
).value
}

@Composable
private fun BottleCanvas(
waterPercentage: Float,
waterColor: Color,
bottleColor: Color,
capColor: Color
) {
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasSize = size
val bottlePath = createBottlePath(canvasSize)

drawBottleWithWater(
bottlePath = bottlePath,
canvasSize = canvasSize,
waterPercentage = waterPercentage,
waterColor = waterColor,
bottleColor = bottleColor
)

drawBottleCap(canvasSize, capColor)
}
}

private fun createBottlePath(canvasSize: Size): Path {
return Path().apply {
with(canvasSize) {
// Bottle neck and body construction
moveTo(width * 0.3f, height * 0.1f)
lineTo(width * 0.3f, height * 0.2f)

// Left curve
quadraticBezierTo(0f, height * 0.3f, 0f, height * 0.4f)
lineTo(0f, height * 0.95f)
quadraticBezierTo(0f, height, width * 0.05f, height)

// Bottom and right side
lineTo(width * 0.95f, height)
quadraticBezierTo(width, height, width, height * 0.95f)
lineTo(width, height * 0.4f)

// Right curve back to neck
quadraticBezierTo(width, height * 0.3f, width * 0.7f, height * 0.2f)
lineTo(width * 0.7f, height * 0.1f)

close()
}
}
}

private fun androidx.compose.ui.graphics.drawscope.DrawScope.drawBottleWithWater(
bottlePath: Path,
canvasSize: Size,
waterPercentage: Float,
waterColor: Color,
bottleColor: Color
) {
clipPath(path = bottlePath) {
// Draw bottle background
drawRect(
color = bottleColor,
size = canvasSize,
topLeft = Offset.Zero
)

// Draw water
val waterPath = createWaterPath(canvasSize, waterPercentage)
drawPath(path = waterPath, color = waterColor)
}
}

private fun createWaterPath(canvasSize: Size, waterPercentage: Float): Path {
val waterLevel = (1 - waterPercentage) * canvasSize.height

return Path().apply {
moveTo(0f, waterLevel)
lineTo(canvasSize.width, waterLevel)
lineTo(canvasSize.width, canvasSize.height)
lineTo(0f, canvasSize.height)
close()
}
}

private fun androidx.compose.ui.graphics.drawscope.DrawScope.drawBottleCap(
canvasSize: Size,
capColor: Color
) {
val capWidth = canvasSize.width * 0.55f
val capHeight = canvasSize.height * 0.13f

drawRoundRect(
color = capColor,
size = Size(capWidth, capHeight),
topLeft = Offset(
x = canvasSize.width / 2 - capWidth / 2f,
y = 0f
),
cornerRadius = CornerRadius(45f, 45f)
)
}

@Composable
private fun WaterAmountText(
usedAmount: Int,
unit: String,
waterPercentage: Float,
textColor: Color
) {
val annotatedText = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = textColor,
fontSize = 44.sp
)
) {
append(usedAmount.toString())
}

withStyle(
style = SpanStyle(
color = textColor,
fontSize = 22.sp
)
) {
append(" $unit")
}
}

Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = annotatedText)
}
}

@Preview
@Composable
fun WaterBottlePreview() {
WatterBottle(
totalWaterAmount = 2500,
unit = "ml",
usedWaterAmount = 120
)
}

Theme.kt
package com.example.waterbottle.ui.theme

import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext

// Color scheme configurations
private object ThemeColors {
val DarkScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)

val LightScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
}

@Composable
fun WaterBottleTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val selectedColorScheme = determineColorScheme(
isDarkTheme = darkTheme,
useDynamicColor = dynamicColor
)

MaterialTheme(
colorScheme = selectedColorScheme,
typography = Typography,
content = content
)
}

@Composable
private fun determineColorScheme(
isDarkTheme: Boolean,
useDynamicColor: Boolean
): ColorScheme {
return when {
useDynamicColor && supportsDynamicColor() -> {
createDynamicColorScheme(isDarkTheme)
}
isDarkTheme -> ThemeColors.DarkScheme
else -> ThemeColors.LightScheme
}
}

private fun supportsDynamicColor(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
}

@Composable
private fun createDynamicColorScheme(isDarkTheme: Boolean): ColorScheme {
val context = LocalContext.current
return if (isDarkTheme) {
dynamicDarkColorScheme(context)
} else {
dynamicLightColorScheme(context)
}
}

Struktur Kode Water Bottle App (Variant)

Komentar

Postingan populer dari blog ini

TUGAS 2 : Jettpack compose : Hello, World!

Pertemuan 3 PPB (A) - Mengenal Composable Aplikasi Selamat Ulang Tahun

Evaluasi Tengah Semester