Penyesuaian Warna ketika Tema Terang dan Penambahan Fitur Validasi Input

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2025-11-07 14:12:33 +07:00
parent 010dee7e6b
commit 637c6089b6
2 changed files with 356 additions and 286 deletions

View File

@ -1,4 +1,4 @@
/* /*
* Copyright (C) 2023 The Android Open Source Project * Copyright (C) 2023 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -14,66 +14,66 @@
* limitations under the License. * limitations under the License.
*/ */
/* /*
UTS - BMI Calculator Project UTS - BMI Calculator Project
Nama : Raihan Ariq Muzakki Nama : Raihan Ariq Muzakki
NPM : 202310715297 NPM : 202310715297
Kelas : F5A5 Kelas : F5A5
*/ */
package com.example.bmicalculator package com.example.bmicalculator
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.bmicalculator.ui.theme.BMICalculatorTheme import com.example.bmicalculator.ui.theme.BMICalculatorTheme
import java.text.DecimalFormat import java.text.DecimalFormat
import kotlin.math.pow import kotlin.math.pow
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge() enableEdgeToEdge()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -87,14 +87,15 @@ class MainActivity : ComponentActivity() {
} }
} }
} }
} }
// Tata letak Tampilan Aplikasi // Tata letak Tampilan Aplikasi
@Composable @Composable
fun BMICalculatorLayout() { fun BMICalculatorLayout() {
var heightInput by remember { mutableStateOf("") } var heightInput by remember { mutableStateOf("") }
var weightInput by remember { mutableStateOf("") } var weightInput by remember { mutableStateOf("") }
var unitUSC by remember { mutableStateOf(false) } var unitUSC by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf("") }
// State baru untuk mengontrol kapan output ditampilkan // State baru untuk mengontrol kapan output ditampilkan
var showResult by remember { mutableStateOf(false) } var showResult by remember { mutableStateOf(false) }
@ -104,6 +105,7 @@ fun BMICalculatorLayout() {
heightInput = "" heightInput = ""
weightInput = "" weightInput = ""
showResult = false showResult = false
errorMessage = ""
} }
val bmiHeight = heightInput.toDoubleOrNull() ?: 0.0 val bmiHeight = heightInput.toDoubleOrNull() ?: 0.0
@ -179,6 +181,57 @@ fun BMICalculatorLayout() {
Button( Button(
onClick = { onClick = {
// Validasi angka
if (bmiHeight == 0.0 || bmiWeight == 0.0) {
errorMessage = "Input harus berupa angka yang valid."
showResult = false
return@Button
}
// Validasi untuk SI Units
if (!unitUSC) {
if (bmiHeight !in 50.0..300.0 && bmiWeight !in 20.0..500.0) {
errorMessage =
"Masukkan Tinggi Badan pada rentang 50-300 cm \ndan\n Berat Badan pada rentang 20-500 kg"
showResult = false
return@Button
}
else if (bmiHeight !in 50.0..300.0) {
errorMessage = "Tinggi harus berada pada rentang 50300 cm."
showResult = false
return@Button
}
else if (bmiWeight !in 20.0..500.0) {
errorMessage = "Berat harus berada pada rentang 20500 kg."
showResult = false
return@Button
}
}
// Validasi untuk USC Units
if (unitUSC) {
if (bmiHeight !in 20.0..120.0 && bmiWeight !in 44.0..1100.0){
errorMessage =
"Masukkan Tinggi Badan pada rentang 20-120 in dan\n Berat Badan pada rentang 44-110 kg"
showResult = false
return@Button
}
else if (bmiHeight !in 20.0..120.0) {
errorMessage = "Tinggi harus berada pada rentang 20120 in."
showResult = false
return@Button
}
else if (bmiWeight !in 44.0..1100.0) {
errorMessage = "Berat harus berada pada rentang 441100 lbs."
showResult = false
return@Button
}
}
// Jika valid
errorMessage = ""
showResult = true showResult = true
}, },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
@ -193,6 +246,7 @@ fun BMICalculatorLayout() {
heightInput = "" heightInput = ""
weightInput = "" weightInput = ""
showResult = false showResult = false
errorMessage = ""
}, },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
@ -202,6 +256,22 @@ fun BMICalculatorLayout() {
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
// Pesan Error
if (errorMessage.isNotEmpty()) {
Text(
text = errorMessage,
color = MaterialTheme.colorScheme.error,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(top = 16.dp)
.fillMaxWidth(),
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.height(32.dp))
// Output BMI
if (showResult) { if (showResult) {
Text( Text(
text = "BMI: $bmi", text = "BMI: $bmi",
@ -222,20 +292,20 @@ fun BMICalculatorLayout() {
Spacer(modifier = Modifier.height(150.dp)) Spacer(modifier = Modifier.height(150.dp))
} }
} }
// Fungsi media Input Tinggi Badan dan Berat Badan // Fungsi media Input Tinggi Badan dan Berat Badan
@Composable @Composable
fun EditNumberField( fun EditNumberField(
@StringRes label: Int, @StringRes label: Int,
@DrawableRes leadingIcon: Int, @DrawableRes leadingIcon: Int,
keyboardOptions: KeyboardOptions, keyboardOptions: KeyboardOptions,
value: String, value: String,
onValueChanged: (String) -> Unit, onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
TextField( TextField(
value = value, value = value,
singleLine = true, singleLine = true,
@ -245,14 +315,14 @@ fun EditNumberField(
label = { Text(stringResource(label)) }, label = { Text(stringResource(label)) },
keyboardOptions = keyboardOptions keyboardOptions = keyboardOptions
) )
} }
@Composable @Composable
fun UnitUSCFormulaRow( fun UnitUSCFormulaRow(
unitUSC: Boolean, unitUSC: Boolean,
onUSCChanged: (Boolean) -> Unit, onUSCChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Row( Row(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@ -266,9 +336,9 @@ fun UnitUSCFormulaRow(
onCheckedChange = onUSCChanged onCheckedChange = onUSCChanged
) )
} }
} }
/** /**
* Calculates the BMI * Calculates the BMI
* dengan Rumus SI Metrics Unit (Default) * dengan Rumus SI Metrics Unit (Default)
* dan * dan
@ -276,7 +346,7 @@ fun UnitUSCFormulaRow(
* *
* Catatan: Unit Testing Sudah ada di src/test/java/calculateBMITest.kt * Catatan: Unit Testing Sudah ada di src/test/java/calculateBMITest.kt
*/ */
fun calculateBMI(bmiHeight: Double, bmiWeight: Double, unitUSC: Boolean): String { fun calculateBMI(bmiHeight: Double, bmiWeight: Double, unitUSC: Boolean): String {
if (bmiHeight <= 0 || bmiWeight <= 0){ if (bmiHeight <= 0 || bmiWeight <= 0){
return "0.0" return "0.0"
} }
@ -290,9 +360,9 @@ fun calculateBMI(bmiHeight: Double, bmiWeight: Double, unitUSC: Boolean): String
val df = DecimalFormat("#.#") val df = DecimalFormat("#.#")
return df.format(bmi) return df.format(bmi)
} }
/** /**
* Calculates the BMI Category * Calculates the BMI Category
* bmi < 18.5 -> Underweight * bmi < 18.5 -> Underweight
* 18.5 <= bmi < 25 -> Normal * 18.5 <= bmi < 25 -> Normal
@ -302,7 +372,7 @@ fun calculateBMI(bmiHeight: Double, bmiWeight: Double, unitUSC: Boolean): String
* Catatan: Unit Testing Sudah ada di src/test/java/calculateBMITest.kt * Catatan: Unit Testing Sudah ada di src/test/java/calculateBMITest.kt
* *
*/ */
fun calculateBMICategory(bmi: String): String { fun calculateBMICategory(bmi: String): String {
val bmiValue = bmi.replace(",", ".").toDoubleOrNull() ?: return "" val bmiValue = bmi.replace(",", ".").toDoubleOrNull() ?: return ""
return when { return when {
@ -312,12 +382,12 @@ fun calculateBMICategory(bmi: String): String {
bmiValue < 30.0 -> "⚠️ Overweight" bmiValue < 30.0 -> "⚠️ Overweight"
else -> "‼️ Obesity" else -> "‼️ Obesity"
} }
} }
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun BMICalculatorLayoutPreview() { fun BMICalculatorLayoutPreview() {
BMICalculatorTheme { BMICalculatorTheme {
BMICalculatorLayout() BMICalculatorLayout()
} }
} }

View File

@ -17,8 +17,8 @@ package com.example.bmicalculator.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFFE3E2DE) val md_theme_light_primary = Color(0xFF1351AA) // Button
val md_theme_light_onPrimary = Color(0xFF1351AA) val md_theme_light_onPrimary = Color(0xFFE3E2DE)
val md_theme_light_primaryContainer = Color(0xFFE3E2DE) val md_theme_light_primaryContainer = Color(0xFFE3E2DE)
val md_theme_light_onPrimaryContainer = Color(0xFF1351AA) val md_theme_light_onPrimaryContainer = Color(0xFF1351AA)
val md_theme_light_secondary = Color(0xFFE3E2DE) val md_theme_light_secondary = Color(0xFFE3E2DE)