Penyesuaian Tata Letak dan Code agar lebih clean

This commit is contained in:
202310715297 RAIHAN ARIQ MUZAKKI 2025-11-07 02:00:11 +07:00
parent 42ff96c754
commit d1bd53eca3
4 changed files with 139 additions and 67 deletions

View File

@ -74,6 +74,7 @@ dependencies {
implementation("androidx.core:core-ktx:1.15.0") implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("junit:junit:4.12") implementation("junit:junit:4.12")
implementation("androidx.compose.foundation:foundation:1.9.4")
// Unit Testing // Unit Testing
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")

View File

@ -29,6 +29,7 @@ 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.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
@ -38,11 +39,14 @@ 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.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
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.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
@ -59,8 +63,10 @@ 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.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.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
@ -90,16 +96,20 @@ fun BMICalculatorLayout() {
var weightInput by remember { mutableStateOf("") } var weightInput by remember { mutableStateOf("") }
var unitUSC by remember { mutableStateOf(false) } var unitUSC by remember { mutableStateOf(false) }
// Reset Nilai Input // State baru untuk mengontrol kapan output ditampilkan
var showResult by remember { mutableStateOf(false) }
// Reset nilai input ketika unit berubah
LaunchedEffect(unitUSC) { LaunchedEffect(unitUSC) {
heightInput = "" heightInput = ""
weightInput = "" weightInput = ""
showResult = false
} }
val BmiHeight = heightInput.toDoubleOrNull() ?: 0.0 val bmiHeight = heightInput.toDoubleOrNull() ?: 0.0
val BmiWeight = weightInput.toDoubleOrNull() ?: 0.0 val bmiWeight = weightInput.toDoubleOrNull() ?: 0.0
val bmi = calculateBMI(BmiHeight, BmiWeight, unitUSC) val bmi = calculateBMI(bmiHeight, bmiWeight, unitUSC)
val category = calculateBMICategory(bmi) val category = calculateBMICategory(bmi)
Column( Column(
modifier = Modifier modifier = Modifier
@ -107,17 +117,28 @@ fun BMICalculatorLayout() {
.padding(horizontal = 40.dp) .padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.safeDrawingPadding(), .safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally
verticalArrangement = Arrangement.Center
) { ) {
Image(
painter = painterResource(R.drawable.health_report),
contentDescription = "BMI Icon",
modifier = Modifier
.padding(top = 24.dp, bottom = 16.dp)
.size(80.dp)
)
Text( Text(
text = stringResource(R.string.calculate_bmi), text = stringResource(R.string.calculate_bmi),
style = MaterialTheme.typography.headlineLarge.copy(fontWeight = FontWeight.Bold),
textAlign = TextAlign.Center,
modifier = Modifier modifier = Modifier
.padding(bottom = 16.dp, top = 40.dp) .fillMaxWidth()
.align(alignment = Alignment.Start) .padding(bottom = 32.dp)
) )
EditNumberField( EditNumberField(
label = if (unitUSC == true) R.string.heightInch else R.string.heightCm, label = if (unitUSC) R.string.heightInch else R.string.heightCm,
leadingIcon = R.drawable.number, leadingIcon = R.drawable.number,
keyboardOptions = KeyboardOptions.Default.copy( keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number, keyboardType = KeyboardType.Number,
@ -125,8 +146,11 @@ fun BMICalculatorLayout() {
), ),
value = heightInput, value = heightInput,
onValueChanged = { heightInput = it }, onValueChanged = { heightInput = it },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(), modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
) )
EditNumberField( EditNumberField(
label = if (unitUSC) R.string.weightPound else R.string.weightKg, label = if (unitUSC) R.string.weightPound else R.string.weightKg,
leadingIcon = R.drawable.number, leadingIcon = R.drawable.number,
@ -136,26 +160,72 @@ fun BMICalculatorLayout() {
), ),
value = weightInput, value = weightInput,
onValueChanged = { weightInput = it }, onValueChanged = { weightInput = it },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(), modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
) )
UnitUSCFormulaRow( UnitUSCFormulaRow(
unitUSC = unitUSC, unitUSC = unitUSC,
onUSCChanged = { unitUSC = it }, onUSCChanged = { unitUSC = it },
modifier = Modifier.padding(bottom = 32.dp) modifier = Modifier.padding(bottom = 24.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
) )
// Button Kalkulasi dan Clear Field Text
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(
onClick = {
showResult = true
},
modifier = Modifier.weight(1f)
) {
Text("Calculate")
}
Spacer(modifier = Modifier.width(16.dp))
Button(
onClick = {
heightInput = ""
weightInput = ""
showResult = false
},
modifier = Modifier.weight(1f)
) {
Text("Clear")
}
}
Spacer(modifier = Modifier.height(32.dp))
if (showResult) {
Text(
text = "BMI: $bmi",
style = MaterialTheme.typography.displaySmall,
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.fillMaxWidth()
)
Text(
text = category,
style = MaterialTheme.typography.displayMedium,
textAlign = TextAlign.Center,
fontWeight = FontWeight.ExtraBold,
modifier = Modifier.fillMaxWidth()
)
}
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(
@ -187,7 +257,7 @@ fun UnitUSCFormulaRow(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text(text = stringResource(R.string.use_usc)) Text(text = stringResource(R.string.use_usc), fontWeight = FontWeight.SemiBold)
Switch( Switch(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -204,18 +274,18 @@ fun UnitUSCFormulaRow(
* dan * dan
* dengan Rumus USC Units * dengan Rumus USC Units
* *
* Catatan: tambahkan unit test untuk kalkulasi BMI ini * 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"
} }
val heightInMeter = BmiHeight/100 // konversi centimeter ke meter val heightInMeter = bmiHeight/100 // konversi centimeter ke meter
var bmi = BmiWeight / heightInMeter.pow(2) var bmi = bmiWeight / heightInMeter.pow(2)
if (unitUSC == true) { if (unitUSC) {
bmi = 703 * (BmiWeight / BmiHeight.pow(2)) bmi = 703 * (bmiWeight / bmiHeight.pow(2))
} }
val df = DecimalFormat("#.#") val df = DecimalFormat("#.#")
@ -229,17 +299,18 @@ fun calculateBMI(BmiHeight: Double, BmiWeight: Double, unitUSC: Boolean): String
* 25 <= bmi <= 30 -> Normal * 25 <= bmi <= 30 -> Normal
* bmi > 30 -> Overweight * bmi > 30 -> Overweight
* *
* Catatan: tambahkan unit test untuk kalkulasi BMI ini * 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 {
bmiValue == 0.0 -> "" bmiValue == 0.0 -> ""
bmiValue < 18.5 -> "Underweight" bmiValue < 18.5 -> "⚠️ Underweight"
bmiValue < 25.0 -> "Normal" bmiValue < 25.0 -> "Normal"
bmiValue < 30.0 -> "Overweight" bmiValue < 30.0 -> "⚠️ Overweight"
else -> "Obesity" else -> "‼️ Obesity"
} }
} }

View File

@ -33,10 +33,10 @@ val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6) val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF) val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002) val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFAFCFF) val md_theme_light_background = Color(0xFFE3E2DE)
val md_theme_light_onBackground = Color(0xFF001F2A) val md_theme_light_onBackground = Color(0xFF1351AA)
val md_theme_light_surface = Color(0xFFFAFCFF) val md_theme_light_surface = Color(0xFFE3E2DE)
val md_theme_light_onSurface = Color(0xFF001F2A) val md_theme_light_onSurface = Color(0xFF1351AA)
val md_theme_light_surfaceVariant = Color(0xFFF2DDE2) val md_theme_light_surfaceVariant = Color(0xFFF2DDE2)
val md_theme_light_onSurfaceVariant = Color(0xFF514347) val md_theme_light_onSurfaceVariant = Color(0xFF514347)
val md_theme_light_outline = Color(0xFF837377) val md_theme_light_outline = Color(0xFF837377)
@ -47,32 +47,32 @@ val md_theme_light_surfaceTint = Color(0xFF984061)
val md_theme_light_outlineVariant = Color(0xFFD5C2C6) val md_theme_light_outlineVariant = Color(0xFFD5C2C6)
val md_theme_light_scrim = Color(0xFF000000) val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFFFFB0C8) val md_theme_dark_primary = Color(0xFFE3E2DE)
val md_theme_dark_onPrimary = Color(0xFF5E1133) val md_theme_dark_onPrimary = Color(0xFF1351AA)
val md_theme_dark_primaryContainer = Color(0xFF7B2949) val md_theme_dark_primaryContainer = Color(0xFFE3E2DE)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFD9E2) val md_theme_dark_onPrimaryContainer = Color(0xFF1351AA)
val md_theme_dark_secondary = Color(0xFFDEB7FF) val md_theme_dark_secondary = Color(0xFFE3E2DE)
val md_theme_dark_onSecondary = Color(0xFF44196A) val md_theme_dark_onSecondary = Color(0xFF1351AA)
val md_theme_dark_secondaryContainer = Color(0xFF5C3382) val md_theme_dark_secondaryContainer = Color(0xFFE3E2DE)
val md_theme_dark_onSecondaryContainer = Color(0xFFF1DBFF) val md_theme_dark_onSecondaryContainer = Color(0xFF1351AA)
val md_theme_dark_tertiary = Color(0xFFFFB1C7) val md_theme_dark_tertiary = Color(0xFFE3E2DE)
val md_theme_dark_onTertiary = Color(0xFF5E1132) val md_theme_dark_onTertiary = Color(0xFF1351AA)
val md_theme_dark_tertiaryContainer = Color(0xFF7B2948) val md_theme_dark_tertiaryContainer = Color(0xFFE3E2DE)
val md_theme_dark_onTertiaryContainer = Color(0xFFFFD9E2) val md_theme_dark_onTertiaryContainer = Color(0xFFE3E2DE)
val md_theme_dark_error = Color(0xFFFFB4AB) val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A) val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005) val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF001F2A) val md_theme_dark_background = Color(0xFF1351AA)
val md_theme_dark_onBackground = Color(0xFFBFE9FF) val md_theme_dark_onBackground = Color(0xFFE3E2DE)
val md_theme_dark_surface = Color(0xFF001F2A) val md_theme_dark_surface = Color(0xFF1351AA) // Warna Background depan
val md_theme_dark_onSurface = Color(0xFFBFE9FF) val md_theme_dark_onSurface = Color(0xFFE3E2DE) // Warna font Background depan
val md_theme_dark_surfaceVariant = Color(0xFF514347) val md_theme_dark_surfaceVariant = Color(0xFFE3E2DE)
val md_theme_dark_onSurfaceVariant = Color(0xFFD5C2C6) val md_theme_dark_onSurfaceVariant = Color(0xFFE3E2DE)
val md_theme_dark_outline = Color(0xFF9E8C90) val md_theme_dark_outline = Color(0xFFE3E2DE) // Warna Opsi
val md_theme_dark_inverseOnSurface = Color(0xFF001F2A) val md_theme_dark_inverseOnSurface = Color(0xFF1351AA)
val md_theme_dark_inverseSurface = Color(0xFFBFE9FF) val md_theme_dark_inverseSurface = Color(0xFFE3E2DE)
val md_theme_dark_inversePrimary = Color(0xFF984061) val md_theme_dark_inversePrimary = Color(0xFFE3E2DE)
val md_theme_dark_surfaceTint = Color(0xFFFFB0C8) val md_theme_dark_surfaceTint = Color(0xFF1351AA)
val md_theme_dark_outlineVariant = Color(0xFF514347) val md_theme_dark_outlineVariant = Color(0xFFE3E2DE)
val md_theme_dark_scrim = Color(0xFF000000) val md_theme_dark_scrim = Color(0xFFE3E2DE)

View File

@ -16,12 +16,12 @@
--> -->
<resources> <resources>
<string name="app_name">BMI Calculator</string> <string name="app_name">BMI Calculator</string>
<string name="calculate_bmi">Calculate BMI</string> <string name="calculate_bmi">BMI Calculator</string>
<string name="heightCm">Tinggi Badan (cm)</string> <string name="heightCm">Tinggi Badan (cm)</string>
<string name="weightKg">Berat Badan (kg)</string> <string name="weightKg">Berat Badan (kg)</string>
<string name="heightInch">Tinggi Badan (inch)</string> <string name="heightInch">Tinggi Badan (inch)</string>
<string name="weightPound">Berat Badan (lbs)</string> <string name="weightPound">Berat Badan (lbs)</string>
<string name="use_usc">Gunakan Unit USC (lbs/in)?</string> <string name="use_usc">USC Units (lbs/in)</string>
<string name="bmi_calculation">BMI Anda: %s</string> <string name="bmi_calculation">BMI -> %s</string>
<string name="bmi_category">Kategori: %s</string> <string name="bmi_category">Kategori -> %s</string>
</resources> </resources>