Compare commits

..

No commits in common. "e065861bd11d0b0fb6de7a985eb461cc0519dddf" and "e33952fa0e26be950b02c185eea7ca31a63bd3ce" have entirely different histories.

25 changed files with 338 additions and 575 deletions

141
README.md
View File

@ -1,120 +1,44 @@
# BMI Calculator (UTS Mobile Programming) # Kalkulator BMI 📱⚖️
**Raihan Ariq Muzakki (202310715297)**
**F5A5 Mobile Programming**
## 👨‍💻 Informasi Pengembang Aplikasi **Kalkulator BMI** ini dikembangkan menggunakan **Android Studio + Jetpack Compose** dengan fokus pada akurasi perhitungan, pengalaman pengguna yang sederhana, serta dukungan dua standar pengukuran: **SI (kg/m²)** dan **USC (lbs/in²)**.
- Syahril Achmad Fahrezi (202310715130)
- **F5A5 Mobile Programming**
---
## 📘 Dokumentasi
- Petunjuk detail dapat dibaca di: https://docs.google.com/document/d/1iGiC0Bg3Bdcd2Maq45TYkCDUkZ5Ql51E/edit?rtpof=true
- Starter dimodifikasi dan terinspirasi dari: https://developer.android.com/codelabs/basic-android-compose-calculate-tip#0
---
## 📚 Referensi
- [kotlin.math](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.math/)
- [Calculate the Power of a Number in Kotlin](https://www.baeldung.com/kotlin/power-of-a-number)
- [Centimeters to Inches](https://www.splashlearn.com/math-vocabulary/cm-to-inches)
- [Kilograms to Pound](https://convertlive.com/id/u/mengkonversi/kilo/ke/pound)
- [Unit Testing](https://medium.com/@deonolarewaju/introduction-to-unit-testing-in-android-kotlin-4331eb2366a9)
- [Icon PNG](https://www.flaticon.com/)
---
## 📱 Deskripsi Proyek
Aplikasi BMI Calculator ini merupakan hasil proyek UTS tentang aplikasi berbasis Jetpack Compose yang berfungsi untuk menghitung Body Mass Index (BMI) menggunakan dua standar perhitungan, yaitu SI (kilogram dan sentimeter) serta USC (pound dan inch). Aplikasi ini menyediakan antarmuka yang sederhana dengan fitur input tinggi serta berat badan, pemilihan standar pengukuran melalui switch, serta tombol untuk menghitung dan menghapus data. Hasil perhitungan ditampilkan dalam format satu angka di belakang koma disertai kategori BMI yang relevan, sehingga pengguna dapat mengetahui status berat badan secara cepat dan akurat. Aplikasi ini juga memanfaatkan komponennya secara modular, termasuk fungsi perhitungan BMI, kategori BMI, dan pengaturan tata letak melalui Jetpack Compose.
--- ---
## ✨ Fitur Utama ## ✨ Fitur Utama
- ✅ Perhitungan BMI berdasarkan dua standar (SI dan USC)
1. **Perhitungan BMI** dengan dua standar satuan (SI & USC). - ✅ Input otomatis ditangani agar **NaN berubah menjadi 0** sebelum kalkulasi
2. **Validasi input** berdasarkan rentang nilai tinggi dan berat badan. - ✅ Format angka otomatis menjadi **1 angka di belakang koma**
3. **Reset otomatis** ketika pengguna beralih standar unit. - ✅ Reset nilai input otomatis menggunakan **LaunchedEffect** saat pengguna berpindah standar
4. **Kategori BMI** secara real-time setelah perhitungan. - ✅ Penentuan kategori BMI dengan kondisi yang lebih bersih & mudah dipahami
5. **Antarmuka berbasis Jetpack Compose** dengan tata letak responsif. - ✅ Desain UI memakai Compose: gambar, tombol aksi (Calculate & Clear), dan tata letak responsif
6. **Unit Testing** untuk fungsi `calculateBMI()` dan `calculateBMICategory()`. - ✅ Termasuk **Unit Testing** untuk fungsi perhitungan BMI
--- ---
## 🗺️ Cara Menjalankan Aplikasi ## 📘 Dokumentasi
Petunjuk detail dapat dibaca di:
### Persyaratan https://docs.google.com/document/d/1iGiC0Bg3Bdcd2Maq45TYkCDUkZ5Ql51E/edit?rtpof=true
- Android Studio sudah versi terbaru (Otter)
- Android SDK dan Build Tools untuk menjalankan Compose
- Perangkat Handphone (Debugging) atau Emulator Android
### Langkah Menjalankan
1. Buka Android Studio
2. Pilih Clone Repository ke
https://git.lab.ubharajaya.ac.id/202310715211-SYAHRIL-ACHMAD-FAHREZI/UTS-SYAHRILACHMADFAHREZI-202310715211.git
3. Pastikan dependensi Gradle terunduh dan tersinkron
4. Pilih perangkat emulator atau perangkat fisik (Debugging) pada menu Device Manager
5. Tekan Run (Shift +F10) untuk menjalankan aplikasi
6. Aplikasi akan tampil di layar emulator atau perangkat debugging, dan siap digunakan untuk memasukkan tinggi badan, berat badan, dan Standar Perhitungan (SI Metric atau USC Units)
Starter dimodifikasi dan terinspirasi dari:
https://developer.android.com/codelabs/basic-android-compose-calculate-tip#0
--- ---
## 🧪 Dokumentasi Unit Testing ## 🔧 Referensi
- Kotlin Math Library
Pengujian dilakukan pada direktori: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.math/
- Perhitungan pangkat pada Kotlin
``` https://www.baeldung.com/kotlin/power-of-a-number
app/src/test/java/calculateBMITest - Konversi cm → inch
``` https://www.splashlearn.com/math-vocabulary/cm-to-inches
- Konversi kg → lbs
Fungsi pengujian: https://convertlive.com/id/u/mengkonversi/kilo/ke/pound
- Unit Testing pada Android
* `calculateBMITest()` https://medium.com/@deonolarewaju/introduction-to-unit-testing-in-android-kotlin-4331eb2366a9
* `calculateBMICategoryTest()` - Ikon PNG
https://www.flaticon.com/
### Fokus Pengujian
1. Validasi hasil perhitungan BMI pada unit SI dan USC.
2. Akurasi kategori BMI.
3. Penanganan input tidak valid (tinggi/berat <= 0).
---
## 🔄 Changelog
Changelog disusun berdasarkan riwayat commit/push pada repository.
### v1.4 Penyempurnaan Fitur & Validasi (TERBARU)
* Penyesuaian warna pada tema terang.
* Penambahan validasi input tinggi dan berat.
* Peningkatan UI pada layout untuk konsistensi.
### v1.3 Pembaruan Tampilan & Ikon
* Perubahan ikon aplikasi.
* Optimalisasi tata letak halaman.
* Pembaruan dokumen README.
### v1.2 Integrasi Unit Testing
* Penambahan direktori dan berkas unit test.
* Pengujian ulang fungsi `calculateBMI()` dan `calculateBMICategory()`.
* Penyempurnaan logika pengkategorian BMI.
### v1.1 Penambahan Fitur Pengukuran USC
* Penyesuaian kalkulasi BMI dengan rumus USC.
* Penambahan switch untuk mengganti standar pengukuran.
* Refactor nama fungsi dan variabel.
### v1.0 Rilis Awal
* Implementasi fungsi dasar kalkulasi BMI (SI Unit).
* Struktur awal proyek Jetpack Compose.
* Penambahan komentar proyek (Nama, NPM, Kelas).
--- ---
@ -131,10 +55,3 @@ Kontribusi yang diberikan:
- Simplifikasi kondisi kategori BMI - Simplifikasi kondisi kategori BMI
- Penyusunan Unit Testing - Penyusunan Unit Testing
- Penyempurnaan layout, penambahan ikon, dan tombol aksi - Penyempurnaan layout, penambahan ikon, dan tombol aksi
- Membantu membuat Validasi Input
---

View File

@ -20,9 +20,9 @@
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/bmi_icon" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/bmi_icon_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.TipTime" android:theme="@style/Theme.TipTime"
tools:targetApi="33"> tools:targetApi="33">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@ -95,7 +95,6 @@
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) }
@ -105,7 +104,6 @@
heightInput = "" heightInput = ""
weightInput = "" weightInput = ""
showResult = false showResult = false
errorMessage = ""
} }
val bmiHeight = heightInput.toDoubleOrNull() ?: 0.0 val bmiHeight = heightInput.toDoubleOrNull() ?: 0.0
@ -181,57 +179,6 @@
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)
@ -246,7 +193,6 @@
heightInput = "" heightInput = ""
weightInput = "" weightInput = ""
showResult = false showResult = false
errorMessage = ""
}, },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
@ -256,22 +202,6 @@
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",

View File

@ -17,18 +17,18 @@ package com.example.bmicalculator.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF1351AA) // Button val md_theme_light_primary = Color(0xFF984061)
val md_theme_light_onPrimary = Color(0xFFE3E2DE) val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFE3E2DE) val md_theme_light_primaryContainer = Color(0xFFFFD9E2)
val md_theme_light_onPrimaryContainer = Color(0xFF1351AA) val md_theme_light_onPrimaryContainer = Color(0xFF3E001D)
val md_theme_light_secondary = Color(0xFFE3E2DE) val md_theme_light_secondary = Color(0xFF754B9C)
val md_theme_light_onSecondary = Color(0xFF1351AA) val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFE3E2DE) val md_theme_light_secondaryContainer = Color(0xFFF1DBFF)
val md_theme_light_onSecondaryContainer = Color(0xFF1351AA) val md_theme_light_onSecondaryContainer = Color(0xFF2D0050)
val md_theme_light_tertiary = Color(0xFFE3E2DE) val md_theme_light_tertiary = Color(0xFF984060)
val md_theme_light_onTertiary = Color(0xFF1351AA) val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFE3E2DE) val md_theme_light_tertiaryContainer = Color(0xFFFFD9E2)
val md_theme_light_onTertiaryContainer = Color(0xFF1351AA) val md_theme_light_onTertiaryContainer = Color(0xFF3E001D)
val md_theme_light_error = Color(0xFFBA1A1A) 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)
@ -38,14 +38,14 @@ val md_theme_light_onBackground = Color(0xFF1351AA)
val md_theme_light_surface = Color(0xFFE3E2DE) val md_theme_light_surface = Color(0xFFE3E2DE)
val md_theme_light_onSurface = Color(0xFF1351AA) 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(0xFF1351AA) val md_theme_light_onSurfaceVariant = Color(0xFF514347)
val md_theme_light_outline = Color(0xFF1351AA) val md_theme_light_outline = Color(0xFF837377)
val md_theme_light_inverseOnSurface = Color(0xFF1351AA) val md_theme_light_inverseOnSurface = Color(0xFFE1F4FF)
val md_theme_light_inverseSurface = Color(0xFF003547) val md_theme_light_inverseSurface = Color(0xFF003547)
val md_theme_light_inversePrimary = Color(0xFF1351AA) val md_theme_light_inversePrimary = Color(0xFFFFB0C8)
val md_theme_light_surfaceTint = Color(0xFF984061) 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(0xFF1351AA) val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFFE3E2DE) val md_theme_dark_primary = Color(0xFFE3E2DE)
val md_theme_dark_onPrimary = Color(0xFF1351AA) val md_theme_dark_onPrimary = Color(0xFF1351AA)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/bmi_icon_background"/>
<foreground android:drawable="@mipmap/bmi_icon_foreground"/>
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/bmi_icon_background"/>
<foreground android:drawable="@mipmap/bmi_icon_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB