UTS
This commit is contained in:
parent
099c35f19a
commit
2010a5b54c
51
README.md
51
README.md
@ -1,10 +1,47 @@
|
||||
Kalkulator BMI
|
||||
===============
|
||||
# BMI Calculator Android App
|
||||
|
||||
Silahkan kembangkan aplikasi ini untuk melakukan perhitungan BMI
|
||||
**NPM**: 202310715043
|
||||
**Nama**: Muhammad Rafly Al Fathir
|
||||
|
||||
Petunjuk lebih detil dapat dibaca di
|
||||
https://docs.google.com/document/d/1iGiC0Bg3Bdcd2Maq45TYkCDUkZ5Ql51E/edit?rtpof=true
|
||||
## Deskripsi
|
||||
Aplikasi Android untuk menghitung Body Mass Index (BMI) berdasarkan tinggi dan berat badan. Aplikasi ini mendukung dua sistem unit: Metrik (cm, kg) dan US Customary (inches, lbs).
|
||||
|
||||
Starter dimodifikasi dan terinspirasi dari:
|
||||
https://developer.android.com/codelabs/basic-android-compose-calculate-tip#0
|
||||
## Fitur
|
||||
- ✅ Perhitungan BMI yang akurat
|
||||
- ✅ Dukungan untuk sistem Metrik dan USC
|
||||
- ✅ Validasi input untuk mencegah nilai yang tidak wajar
|
||||
- ✅ Kategori BMI (Underweight, Normal, Overweight, Obese)
|
||||
- ✅ UI modern dengan Material Design 3
|
||||
- ✅ Color coding untuk kategori BMI
|
||||
- ✅ Informasi lengkap tentang kategori BMI
|
||||
|
||||
## Teknologi yang Digunakan
|
||||
- **Kotlin**: Bahasa pemrograman utama
|
||||
- **Jetpack Compose**: UI Framework
|
||||
- **Material Design 3**: Design system
|
||||
- **JUnit**: Unit testing
|
||||
|
||||
## Formula BMI
|
||||
```
|
||||
BMI = Berat (kg) / (Tinggi (m))²
|
||||
```
|
||||
|
||||
## Kategori BMI (WHO)
|
||||
- **Underweight**: BMI < 18.5
|
||||
- **Normal**: 18.5 ≤ BMI < 25
|
||||
- **Overweight**: 25 ≤ BMI < 30
|
||||
- **Obese**: BMI ≥ 30
|
||||
|
||||
## Unit Testing
|
||||
Aplikasi dilengkapi dengan unit test untuk:
|
||||
- Perhitungan BMI (sistem metrik dan USC)
|
||||
- Kategori BMI
|
||||
- Validasi input
|
||||
- Boundary cases
|
||||
|
||||
## Kontribusi & Kredit
|
||||
Aplikasi ini dikembangkan dengan bantuan Claude ai dalam pembuatan kode, desain antarmuka, dan dokumentasi.
|
||||
|
||||
|
||||
## Lisensi
|
||||
Apache License 2.0
|
||||
@ -17,10 +17,10 @@ package com.example.tiptime
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -30,17 +30,28 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -48,14 +59,15 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.tiptime.ui.theme.TipTimeTheme
|
||||
import java.text.NumberFormat
|
||||
import kotlin.math.pow
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -66,82 +78,294 @@ class MainActivity : ComponentActivity() {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
TipTimeLayout()
|
||||
// State untuk menentukan halaman mana yang ditampilkan
|
||||
var showWelcomeScreen by remember { mutableStateOf(true) }
|
||||
|
||||
if (showWelcomeScreen) {
|
||||
// Tampilkan Welcome Screen
|
||||
WelcomeScreen(
|
||||
onStartClick = {
|
||||
showWelcomeScreen = false // Pindah ke halaman BMI Calculator
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// Tampilkan BMI Calculator dengan tombol back
|
||||
BMICalculatorLayout(
|
||||
onBackClick = {
|
||||
showWelcomeScreen = true // Kembali ke Welcome Screen
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout utama untuk aplikasi BMI Calculator
|
||||
* Menampilkan input field untuk tinggi dan berat badan,
|
||||
* toggle untuk unit sistem, dan hasil perhitungan BMI
|
||||
*
|
||||
* @param onBackClick Callback yang dipanggil ketika tombol back diklik
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TipTimeLayout() {
|
||||
var amountInput by remember { mutableStateOf("") }
|
||||
var tipInput by remember { mutableStateOf("") }
|
||||
var roundUp by remember { mutableStateOf(false) }
|
||||
fun BMICalculatorLayout(onBackClick: () -> Unit = {}) {
|
||||
var heightInput by remember { mutableStateOf("") }
|
||||
var weightInput by remember { mutableStateOf("") }
|
||||
var useUSC by remember { mutableStateOf(false) }
|
||||
|
||||
val BmiHeight = amountInput.toDoubleOrNull() ?: 0.0
|
||||
val BmiWeight = tipInput.toDoubleOrNull() ?: 0.0
|
||||
val bmi = calculateBMI(BmiHeight, BmiWeight, roundUp)
|
||||
val category = calculateBMICategory(BmiHeight, BmiWeight, roundUp)
|
||||
// Konversi input ke Double atau null jika tidak valid
|
||||
val height = heightInput.toDoubleOrNull() ?: 0.0
|
||||
val weight = weightInput.toDoubleOrNull() ?: 0.0
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 40.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.safeDrawingPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.calculate_tip),
|
||||
// Validasi input
|
||||
val inputError = validateInput(height, weight, useUSC)
|
||||
|
||||
// Hitung BMI dan kategori jika input valid
|
||||
val bmi = if (inputError == null) {
|
||||
calculateBMI(height, weight, useUSC)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
|
||||
val category = if (inputError == null && bmi > 0) {
|
||||
calculateBMICategory(bmi)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val categoryColor = getBMICategoryColor(category)
|
||||
|
||||
// Gunakan Scaffold untuk layout dengan TopAppBar
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = "BMI Calculator",
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBackClick) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Back to Welcome Screen",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp, top = 40.dp)
|
||||
.align(alignment = Alignment.Start)
|
||||
)
|
||||
EditNumberField(
|
||||
label = R.string.height,
|
||||
leadingIcon = R.drawable.number,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
value = amountInput,
|
||||
onValueChanged = { amountInput = it },
|
||||
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(),
|
||||
)
|
||||
EditNumberField(
|
||||
label = R.string.weight,
|
||||
leadingIcon = R.drawable.number,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
value = tipInput,
|
||||
onValueChanged = { tipInput = it },
|
||||
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(),
|
||||
)
|
||||
RoundTheTipRow(
|
||||
roundUp = roundUp,
|
||||
onRoundUpChanged = { roundUp = it },
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.bmi_calculation, bmi),
|
||||
style = MaterialTheme.typography.displaySmall
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.bmi_category, category),
|
||||
style = MaterialTheme.typography.displaySmall
|
||||
)
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(horizontal = 24.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.safeDrawingPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Spacer(modifier = Modifier.height(150.dp))
|
||||
// Subtitle
|
||||
Text(
|
||||
text = "Calculate your Body Mass Index",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 24.dp)
|
||||
.align(alignment = Alignment.Start)
|
||||
)
|
||||
|
||||
// Input Field untuk Tinggi
|
||||
EditNumberField(
|
||||
label = if (useUSC) "Height (inches)" else "Height (cm)",
|
||||
leadingIcon = R.drawable.number,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Decimal,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
value = heightInput,
|
||||
onValueChanged = { heightInput = it },
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// Input Field untuk Berat
|
||||
EditNumberField(
|
||||
label = if (useUSC) "Weight (lbs)" else "Weight (kg)",
|
||||
leadingIcon = R.drawable.number,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Decimal,
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
value = weightInput,
|
||||
onValueChanged = { weightInput = it },
|
||||
modifier = Modifier
|
||||
.padding(bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// Toggle untuk Unit Sistem
|
||||
UnitSystemToggleRow(
|
||||
useUSC = useUSC,
|
||||
onToggleChanged = { useUSC = it },
|
||||
modifier = Modifier.padding(bottom = 24.dp)
|
||||
)
|
||||
|
||||
// Tampilkan error jika ada
|
||||
if (inputError != null) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = inputError,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Tampilkan hasil BMI jika valid
|
||||
if (inputError == null && bmi > 0) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "Your BMI",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
|
||||
Text(
|
||||
text = String.format("%.1f", bmi),
|
||||
style = MaterialTheme.typography.displayLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
)
|
||||
|
||||
if (category.isNotEmpty()) {
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = categoryColor
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = category,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Informasi Kategori BMI
|
||||
BMICategoryInfo()
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable untuk menampilkan informasi kategori BMI
|
||||
*/
|
||||
@Composable
|
||||
fun BMICategoryInfo() {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "BMI Categories",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
BMICategoryItem("Underweight", "< 18.5", Color(0xFF2196F3))
|
||||
BMICategoryItem("Normal", "18.5 - 24.9", Color(0xFF4CAF50))
|
||||
BMICategoryItem("Overweight", "25.0 - 29.9", Color(0xFFFFA726))
|
||||
BMICategoryItem("Obese", "≥ 30.0", Color(0xFFF44336))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable untuk menampilkan satu item kategori BMI
|
||||
*/
|
||||
@Composable
|
||||
fun BMICategoryItem(category: String, range: String, color: Color) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Card(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = color)
|
||||
) {
|
||||
Spacer(modifier = Modifier
|
||||
.height(16.dp)
|
||||
.padding(horizontal = 8.dp))
|
||||
}
|
||||
Text(text = category, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
Text(
|
||||
text = range,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable untuk input field angka dengan label dan icon
|
||||
*/
|
||||
@Composable
|
||||
fun EditNumberField(
|
||||
@StringRes label: Int,
|
||||
label: String,
|
||||
@DrawableRes leadingIcon: Int,
|
||||
keyboardOptions: KeyboardOptions,
|
||||
value: String,
|
||||
@ -151,64 +375,163 @@ fun EditNumberField(
|
||||
TextField(
|
||||
value = value,
|
||||
singleLine = true,
|
||||
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = leadingIcon),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
onValueChange = onValueChanged,
|
||||
label = { Text(stringResource(label)) },
|
||||
keyboardOptions = keyboardOptions
|
||||
label = { Text(label) },
|
||||
keyboardOptions = keyboardOptions,
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable untuk row toggle unit sistem (Metrik/USC)
|
||||
*/
|
||||
@Composable
|
||||
fun RoundTheTipRow(
|
||||
roundUp: Boolean,
|
||||
onRoundUpChanged: (Boolean) -> Unit,
|
||||
fun UnitSystemToggleRow(
|
||||
useUSC: Boolean,
|
||||
onToggleChanged: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(text = stringResource(R.string.use_usc))
|
||||
Column {
|
||||
Text(
|
||||
text = "Unit System",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = if (useUSC) "US Customary (in, lbs)" else "Metric (cm, kg)",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentWidth(Alignment.End),
|
||||
checked = roundUp,
|
||||
onCheckedChange = onRoundUpChanged
|
||||
checked = useUSC,
|
||||
onCheckedChange = onToggleChanged
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the BMI
|
||||
* Validasi input tinggi dan berat badan
|
||||
*
|
||||
* Catatan: tambahkan unit test untuk kalkulasi BMI ini
|
||||
* @param height Tinggi badan dalam cm atau inches
|
||||
* @param weight Berat badan dalam kg atau lbs
|
||||
* @param useUSC True jika menggunakan unit USC, false untuk metrik
|
||||
* @return String error message jika tidak valid, null jika valid
|
||||
*/
|
||||
private fun calculateBMI(BmiHeight: Double, BmiWeight: Double = 15.0, roundUp: Boolean): String {
|
||||
var bmi = BmiWeight / 100 * BmiHeight
|
||||
if (roundUp) {
|
||||
bmi = kotlin.math.ceil(bmi)
|
||||
fun validateInput(height: Double, weight: Double, useUSC: Boolean): String? {
|
||||
if (height <= 0 || weight <= 0) {
|
||||
return "Please enter valid height and weight"
|
||||
}
|
||||
return NumberFormat.getNumberInstance().format(bmi)
|
||||
}
|
||||
/**
|
||||
* Calculates the BMI Category
|
||||
*
|
||||
* Catatan: tambahkan unit test untuk kalkulasi BMI ini
|
||||
*/
|
||||
|
||||
private fun calculateBMICategory(BmiHeight: Double, BmiWeight: Double = 15.0, roundUp: Boolean): String {
|
||||
var bmi = BmiWeight / 100 * BmiHeight
|
||||
if (roundUp) {
|
||||
bmi = kotlin.math.ceil(bmi)
|
||||
// Validasi untuk sistem metrik (cm, kg)
|
||||
if (!useUSC) {
|
||||
if (height < 50 || height > 300) {
|
||||
return "Height must be between 50-300 cm"
|
||||
}
|
||||
if (weight < 10 || weight > 500) {
|
||||
return "Weight must be between 10-500 kg"
|
||||
}
|
||||
}
|
||||
return NumberFormat.getNumberInstance().format(bmi)
|
||||
// Validasi untuk sistem USC (inches, lbs)
|
||||
else {
|
||||
if (height < 20 || height > 120) {
|
||||
return "Height must be between 20-120 inches"
|
||||
}
|
||||
if (weight < 20 || weight > 1100) {
|
||||
return "Weight must be between 20-1100 lbs"
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Menghitung BMI (Body Mass Index)
|
||||
*
|
||||
* Formula BMI: weight (kg) / (height (m))^2
|
||||
*
|
||||
* @param height Tinggi badan dalam cm (metrik) atau inches (USC)
|
||||
* @param weight Berat badan dalam kg (metrik) atau lbs (USC)
|
||||
* @param useUSC True jika menggunakan unit USC, false untuk metrik
|
||||
* @return Nilai BMI
|
||||
*/
|
||||
fun calculateBMI(height: Double, weight: Double, useUSC: Boolean): Double {
|
||||
if (height <= 0 || weight <= 0) return 0.0
|
||||
|
||||
val heightInMeters: Double
|
||||
val weightInKg: Double
|
||||
|
||||
if (useUSC) {
|
||||
// Konversi dari inches ke meter dan lbs ke kg
|
||||
heightInMeters = height * 0.0254
|
||||
weightInKg = weight * 0.453592
|
||||
} else {
|
||||
// Konversi dari cm ke meter
|
||||
heightInMeters = height / 100.0
|
||||
weightInKg = weight
|
||||
}
|
||||
|
||||
// Formula BMI: weight / height^2
|
||||
return weightInKg / heightInMeters.pow(2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Menentukan kategori BMI berdasarkan nilai BMI
|
||||
*
|
||||
* Kategori WHO:
|
||||
* - Underweight: BMI < 18.5
|
||||
* - Normal weight: 18.5 ≤ BMI < 25
|
||||
* - Overweight: 25 ≤ BMI < 30
|
||||
* - Obese: BMI ≥ 30
|
||||
*
|
||||
* @param bmi Nilai BMI
|
||||
* @return Kategori BMI dalam bentuk String
|
||||
*/
|
||||
fun calculateBMICategory(bmi: Double): String {
|
||||
return when {
|
||||
bmi < 18.5 -> "Underweight"
|
||||
bmi < 25.0 -> "Normal"
|
||||
bmi < 30.0 -> "Overweight"
|
||||
else -> "Obese"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mendapatkan warna untuk kategori BMI
|
||||
*
|
||||
* @param category Kategori BMI
|
||||
* @return Color yang sesuai dengan kategori
|
||||
*/
|
||||
fun getBMICategoryColor(category: String): Color {
|
||||
return when (category) {
|
||||
"Underweight" -> Color(0xFF2196F3) // Blue
|
||||
"Normal" -> Color(0xFF4CAF50) // Green
|
||||
"Overweight" -> Color(0xFFFFA726) // Orange
|
||||
"Obese" -> Color(0xFFF44336) // Red
|
||||
else -> Color.Gray
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun TipTimeLayoutPreview() {
|
||||
fun BMICalculatorLayoutPreview() {
|
||||
TipTimeTheme {
|
||||
TipTimeLayout()
|
||||
BMICalculatorLayout()
|
||||
}
|
||||
}
|
||||
236
app/src/main/java/com/example/tiptime/WelcomeScreen.kt
Normal file
236
app/src/main/java/com/example/tiptime/WelcomeScreen.kt
Normal file
@ -0,0 +1,236 @@
|
||||
package com.example.tiptime
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
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 com.example.tiptime.ui.theme.TipTimeTheme
|
||||
|
||||
/**
|
||||
* Composable untuk halaman Welcome Screen
|
||||
*
|
||||
* @param onStartClick Callback yang dipanggil ketika tombol "MULAI" diklik
|
||||
*/
|
||||
@Composable
|
||||
fun WelcomeScreen(onStartClick: () -> Unit) {
|
||||
// Animasi scale untuk efek zoom-in saat halaman muncul
|
||||
val scale = remember { Animatable(0.8f) }
|
||||
|
||||
// Jalankan animasi saat composable pertama kali ditampilkan
|
||||
LaunchedEffect(Unit) {
|
||||
scale.animateTo(
|
||||
targetValue = 1f,
|
||||
animationSpec = tween(durationMillis = 800)
|
||||
)
|
||||
}
|
||||
|
||||
// Background dengan warna theme
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(32.dp)
|
||||
.scale(scale.value),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
// Icon/Logo Aplikasi
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.padding(bottom = 24.dp),
|
||||
shape = RoundedCornerShape(60.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.number),
|
||||
contentDescription = "BMI Calculator Icon",
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Judul Aplikasi
|
||||
Text(
|
||||
text = "BMI Calculator",
|
||||
fontSize = 36.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Body Mass Index Calculator",
|
||||
fontSize = 16.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 32.dp)
|
||||
)
|
||||
|
||||
// Card Biodata Pengembang
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 32.dp),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "👨💻 Dikembangkan Oleh",
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
// NPM
|
||||
InfoRow(
|
||||
label = "NPM",
|
||||
value = "202310715043"
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// Nama
|
||||
InfoRow(
|
||||
label = "Nama",
|
||||
value = "M Rafly Al Fathir"
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// Program Studi (opsional)
|
||||
InfoRow(
|
||||
label = "Prodi",
|
||||
value = "Teknik Informatika"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Deskripsi Singkat
|
||||
Text(
|
||||
text = "Aplikasi untuk menghitung Body Mass Index (BMI) berdasarkan tinggi dan berat badan Anda dengan dukungan sistem Metrik dan US Customary.",
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
|
||||
// Tombol MULAI
|
||||
Button(
|
||||
onClick = onStartClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary
|
||||
),
|
||||
elevation = ButtonDefaults.buttonElevation(
|
||||
defaultElevation = 4.dp,
|
||||
pressedElevation = 8.dp
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "MULAI",
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
letterSpacing = 1.2.sp
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Copyright
|
||||
Text(
|
||||
text = "© 2024 BMI Calculator App",
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable untuk menampilkan baris informasi (label: value)
|
||||
*/
|
||||
@Composable
|
||||
fun InfoRow(label: String, value: String) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
fontSize = 18.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun WelcomeScreenPreview() {
|
||||
TipTimeTheme {
|
||||
WelcomeScreen(onStartClick = {})
|
||||
}
|
||||
}
|
||||
9
app/src/main/res/drawable/ic_height.xml
Normal file
9
app/src/main/res/drawable/ic_height.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M13,6.99h3L12,3L8,6.99h3v10.02H8L12,21l4,-3.99h-3z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_weight.xml
Normal file
9
app/src/main/res/drawable/ic_weight.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,3C10.34,3 9,4.34 9,6c0,1.1 0.6,2.05 1.48,2.58L7.08,18H4v3h16v-3h-3.08l-3.4,-9.42C14.4,8.05 15,7.1 15,6C15,4.34 13.66,3 12,3zM12,5c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1S11.45,5 12,5z"/>
|
||||
</vector>
|
||||
@ -1,25 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name">BMI Calculator</string>
|
||||
<string name="calculate_tip">Calculate BMI</string>
|
||||
<string name="height">Tinggi Badan</string>
|
||||
<string name="weight">Berat Badan</string>
|
||||
<string name="use_usc">Gunakan Unit USC (lbs/in)?</string>
|
||||
<string name="bmi_calculation">BMI Anda: %s</string>
|
||||
<string name="bmi_category">Kategori: %s</string>
|
||||
</resources>
|
||||
<string name="app_title">BMI Calculator</string>
|
||||
<string name="app_subtitle">Calculate your Body Mass Index</string>
|
||||
|
||||
<!-- Input Labels -->
|
||||
<string name="height_cm">Height (cm)</string>
|
||||
<string name="height_inches">Height (inches)</string>
|
||||
<string name="weight_kg">Weight (kg)</string>
|
||||
<string name="weight_lbs">Weight (lbs)</string>
|
||||
|
||||
<!-- Unit System -->
|
||||
<string name="unit_system">Unit System</string>
|
||||
|
||||
<!-- Results -->
|
||||
<string name="your_bmi">Your BMI</string>
|
||||
<string name="bmi_categories_title">BMI Categories</string>
|
||||
</resources>
|
||||
338
app/src/test/java/com/example/tiptime/BMICalculatorTest.kt
Normal file
338
app/src/test/java/com/example/tiptime/BMICalculatorTest.kt
Normal file
@ -0,0 +1,338 @@
|
||||
/**
|
||||
* Unit tests untuk BMI Calculator
|
||||
*
|
||||
* NPM: [ISI NPM ANDA]
|
||||
* Nama: [ISI NAMA ANDA]
|
||||
*
|
||||
* Deskripsi: File ini berisi unit test untuk menguji fungsi-fungsi
|
||||
* perhitungan BMI, kategori BMI, dan validasi input.
|
||||
*
|
||||
* Cara menjalankan test:
|
||||
* 1. Di Android Studio, klik kanan pada file ini
|
||||
* 2. Pilih "Run 'BMICalculatorTest'"
|
||||
* Atau jalankan via terminal: ./gradlew test
|
||||
*/
|
||||
|
||||
package com.example.tiptime
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Unit Test Class untuk BMI Calculator
|
||||
* Menguji semua fungsi kalkulasi dan validasi
|
||||
*/
|
||||
class BMICalculatorTest {
|
||||
|
||||
/**
|
||||
* Test 1: Menguji perhitungan BMI dengan sistem metrik (cm, kg)
|
||||
* Input: Tinggi 170 cm, Berat 70 kg
|
||||
* Expected Output: BMI = 24.22
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMI_metricSystem_returnsCorrectValue() {
|
||||
// Arrange (Persiapan)
|
||||
val height = 170.0
|
||||
val weight = 70.0
|
||||
val useUSC = false
|
||||
|
||||
// Act (Eksekusi)
|
||||
val result = calculateBMI(height, weight, useUSC)
|
||||
|
||||
// Assert (Verifikasi)
|
||||
// BMI = 70 / (1.7 * 1.7) = 24.22
|
||||
assertEquals(24.22, result, 0.01)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 2: Menguji perhitungan BMI dengan sistem USC (inches, lbs)
|
||||
* Input: Tinggi 67 inches, Berat 154 lbs
|
||||
* Expected Output: BMI ≈ 24.12
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMI_uscSystem_returnsCorrectValue() {
|
||||
val height = 67.0
|
||||
val weight = 154.0
|
||||
val useUSC = true
|
||||
|
||||
val result = calculateBMI(height, weight, useUSC)
|
||||
|
||||
// Konversi: 67 in = 1.7018 m, 154 lbs = 69.85 kg
|
||||
// BMI = 69.85 / (1.7018)^2 = 24.12
|
||||
assertEquals(24.12, result, 0.1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 3: Menguji edge case - tinggi = 0
|
||||
* Input: Tinggi 0 cm
|
||||
* Expected Output: BMI = 0 (untuk menghindari division by zero)
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMI_zeroHeight_returnsZero() {
|
||||
val result = calculateBMI(0.0, 70.0, false)
|
||||
assertEquals(0.0, result, 0.01)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 4: Menguji edge case - berat = 0
|
||||
* Input: Berat 0 kg
|
||||
* Expected Output: BMI = 0
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMI_zeroWeight_returnsZero() {
|
||||
val result = calculateBMI(170.0, 0.0, false)
|
||||
assertEquals(0.0, result, 0.01)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 5: Menguji kategori BMI - Underweight
|
||||
* Input: BMI = 17.5
|
||||
* Expected Output: "Underweight"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_underweight_returnsCorrectCategory() {
|
||||
val bmi = 17.5
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Underweight", result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 6: Menguji kategori BMI - Normal
|
||||
* Input: BMI = 22.0
|
||||
* Expected Output: "Normal"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_normal_returnsCorrectCategory() {
|
||||
val bmi = 22.0
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Normal", result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 7: Menguji kategori BMI - Overweight
|
||||
* Input: BMI = 27.5
|
||||
* Expected Output: "Overweight"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_overweight_returnsCorrectCategory() {
|
||||
val bmi = 27.5
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Overweight", result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 8: Menguji kategori BMI - Obese
|
||||
* Input: BMI = 32.0
|
||||
* Expected Output: "Obese"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_obese_returnsCorrectCategory() {
|
||||
val bmi = 32.0
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Obese", result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 9: Menguji boundary case - BMI = 18.5 (batas Normal)
|
||||
* Input: BMI = 18.5
|
||||
* Expected Output: "Normal"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_boundaryCase_18point5_returnsNormal() {
|
||||
val bmi = 18.5
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Normal", result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 10: Menguji boundary case - BMI = 25.0 (batas Overweight)
|
||||
* Input: BMI = 25.0
|
||||
* Expected Output: "Overweight"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_boundaryCase_25_returnsOverweight() {
|
||||
val bmi = 25.0
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Overweight", result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 11: Menguji boundary case - BMI = 30.0 (batas Obese)
|
||||
* Input: BMI = 30.0
|
||||
* Expected Output: "Obese"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_boundaryCase_30_returnsObese() {
|
||||
val bmi = 30.0
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Obese", result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 12: Menguji validasi input yang valid (metrik)
|
||||
* Input: Tinggi 170 cm, Berat 70 kg
|
||||
* Expected Output: null (tidak ada error)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_validMetricInput_returnsNull() {
|
||||
val result = validateInput(170.0, 70.0, false)
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 13: Menguji validasi input yang valid (USC)
|
||||
* Input: Tinggi 67 inches, Berat 154 lbs
|
||||
* Expected Output: null (tidak ada error)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_validUSCInput_returnsNull() {
|
||||
val result = validateInput(67.0, 154.0, true)
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 14: Menguji validasi input - tinggi = 0
|
||||
* Input: Tinggi 0 cm
|
||||
* Expected Output: error message (not null)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_zeroHeight_returnsError() {
|
||||
val result = validateInput(0.0, 70.0, false)
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 15: Menguji validasi input - berat = 0
|
||||
* Input: Berat 0 kg
|
||||
* Expected Output: error message (not null)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_zeroWeight_returnsError() {
|
||||
val result = validateInput(170.0, 0.0, false)
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 16: Menguji validasi input - tinggi terlalu rendah (metrik)
|
||||
* Input: Tinggi 40 cm (tidak realistis)
|
||||
* Expected Output: error message (not null)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_heightTooLowMetric_returnsError() {
|
||||
val result = validateInput(40.0, 70.0, false)
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 17: Menguji validasi input - tinggi terlalu tinggi (metrik)
|
||||
* Input: Tinggi 350 cm (tidak realistis)
|
||||
* Expected Output: error message (not null)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_heightTooHighMetric_returnsError() {
|
||||
val result = validateInput(350.0, 70.0, false)
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 18: Menguji validasi input - berat terlalu rendah (metrik)
|
||||
* Input: Berat 5 kg (tidak realistis)
|
||||
* Expected Output: error message (not null)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_weightTooLowMetric_returnsError() {
|
||||
val result = validateInput(170.0, 5.0, false)
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 19: Menguji validasi input - berat terlalu tinggi (metrik)
|
||||
* Input: Berat 600 kg (tidak realistis)
|
||||
* Expected Output: error message (not null)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_weightTooHighMetric_returnsError() {
|
||||
val result = validateInput(170.0, 600.0, false)
|
||||
assertNotNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 20: Menguji perhitungan BMI dengan nilai real world
|
||||
* Input: Tinggi 165 cm, Berat 60 kg
|
||||
* Expected Output: BMI = 22.04
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMI_realWorldExample1_returnsCorrectValue() {
|
||||
val height = 165.0
|
||||
val weight = 60.0
|
||||
val useUSC = false
|
||||
|
||||
val result = calculateBMI(height, weight, useUSC)
|
||||
|
||||
// BMI = 60 / (1.65 * 1.65) = 22.04
|
||||
assertEquals(22.04, result, 0.01)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 21: Menguji perhitungan BMI dengan nilai real world lainnya
|
||||
* Input: Tinggi 180 cm, Berat 85 kg
|
||||
* Expected Output: BMI = 26.23
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMI_realWorldExample2_returnsCorrectValue() {
|
||||
val height = 180.0
|
||||
val weight = 85.0
|
||||
val useUSC = false
|
||||
|
||||
val result = calculateBMI(height, weight, useUSC)
|
||||
|
||||
// BMI = 85 / (1.8 * 1.8) = 26.23
|
||||
assertEquals(26.23, result, 0.01)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 22: Menguji validasi input dengan nilai boundary - tinggi minimum valid
|
||||
* Input: Tinggi 50 cm (batas minimum)
|
||||
* Expected Output: null (valid)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_minimumHeightMetric_returnsNull() {
|
||||
val result = validateInput(50.0, 70.0, false)
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 23: Menguji validasi input dengan nilai boundary - tinggi maksimum valid
|
||||
* Input: Tinggi 300 cm (batas maksimum)
|
||||
* Expected Output: null (valid)
|
||||
*/
|
||||
@Test
|
||||
fun validateInput_maximumHeightMetric_returnsNull() {
|
||||
val result = validateInput(300.0, 70.0, false)
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 24: Menguji kategori BMI dengan nilai sangat rendah
|
||||
* Input: BMI = 10.0
|
||||
* Expected Output: "Underweight"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_veryLowBMI_returnsUnderweight() {
|
||||
val bmi = 10.0
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Underweight", result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 25: Menguji kategori BMI dengan nilai sangat tinggi
|
||||
* Input: BMI = 50.0
|
||||
* Expected Output: "Obese"
|
||||
*/
|
||||
@Test
|
||||
fun calculateBMICategory_veryHighBMI_returnsObese() {
|
||||
val bmi = 50.0
|
||||
val result = calculateBMICategory(bmi)
|
||||
assertEquals("Obese", result)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user