diff --git a/README.md b/README.md
index 08d4aa4..3225df6 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,68 @@
-Kalkulator BMI
-===============
+# π± Aplikasi Kalkulator BMI
-Silahkan kembangkan aplikasi ini untuk melakukan perhitungan BMI
+**Dibuat oleh:**
+π¨βπ» **Rafi Fattan Fitriardi**
+π **NIM: 202310715002**
+π« **Pemrograman Perangkat Bergerak - F5A5**
-Petunjuk lebih detil dapat dibaca di
-https://docs.google.com/document/d/1iGiC0Bg3Bdcd2Maq45TYkCDUkZ5Ql51E/edit?rtpof=true
+---
+
+## π Deskripsi Aplikasi
+Aplikasi **Kalkulator BMI (Body Mass Index)** ini dibuat sebagai proyek akhir mata kuliah **Pemrograman Perangkat Bergerak**.
+Tujuan utama aplikasi ini adalah membantu pengguna menghitung **Indeks Massa Tubuh (BMI)** berdasarkan **berat badan (kg)** dan **tinggi badan (cm)** untuk mengetahui apakah berat badan tergolong **kurang, ideal, berlebih, atau obesitas**.
+
+Aplikasi ini memiliki **dua halaman utama**:
+1. **Halaman Biodata Pengembang** β menampilkan informasi pembuat aplikasi (nama, NIM, kelas, dan foto), serta tombol **βMULAIβ** untuk berpindah ke laman utama.
+2. **Halaman Utama (Kalkulator BMI)** β tempat pengguna menginput berat dan tinggi badan, menekan tombol **βHitung BMIβ**, lalu melihat hasil nilai BMI beserta kategori dan saran kesehatannya.
+
+---
+
+## βοΈ Fitur Utama
+- Input berat dan tinggi badan secara interaktif.
+- Perhitungan otomatis nilai BMI.
+- Tampilan kategori hasil (Kurus, Normal, Gemuk, Obesitas).
+- Antarmuka sederhana dan responsif.
+- Navigasi antarhalaman menggunakan tombol **MULAI** dari halaman biodata.
+
+---
+
+## π§© Teknologi yang Digunakan
+- **Android Studio (Kotlin)**
+- **XML Layouts** untuk desain antarmuka
+- **Intent** untuk navigasi antar activity
+- **Drawable XML** untuk gradasi dan tema warna aplikasi
+
+---
+
+## π‘ Struktur Proyek
+```
+app/
+ βββ java/com/example/bmiapp/
+ β βββ SplashActivity.kt // Halaman biodata pengembang
+ β βββ MainActivity.kt // Halaman utama kalkulator BMI
+ β
+ βββ res/
+ β βββ layout/
+ β β βββ activity_splash.xml
+ β β βββ activity_main.xml
+ β βββ drawable/
+ β β βββ splash_gradient.xml
+ β βββ mipmap/
+ β β βββ ic_launcher.png // Ikon aplikasi
+ β β βββ ic_launcher_round.png
+ β βββ values/
+ β βββ colors.xml
+ β βββ strings.xml
+ β βββ themes.xml
+ β
+ βββ AndroidManifest.xml
+```
+
+---
+
+## π§ Kontribusi & Kredit
+Aplikasi ini dikembangkan dengan bantuan **ChatGPT (OpenAI)** dalam pembuatan kode, desain antarmuka, dan penyusunan dokumentasi.
+Semua logika, pengujian, dan penyempurnaan dilakukan secara mandiri oleh pengembang.
+
+---
-Starter dimodifikasi dan terinspirasi dari:
-https://developer.android.com/codelabs/basic-android-compose-calculate-tip#0
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f786714..cf6fcac 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -73,7 +73,7 @@ dependencies {
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
-
+ implementation("androidx.appcompat:appcompat:1.7.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.12.01"))
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e897dec..08036cb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -26,15 +26,24 @@
android:supportsRtl="true"
android:theme="@style/Theme.TipTime"
tools:targetApi="33">
+
+
+
+
+
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar">
+
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..9536c30
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/example/tiptime/MainActivity.kt b/app/src/main/java/com/example/tiptime/MainActivity.kt
index d0fdd80..e05f23a 100644
--- a/app/src/main/java/com/example/tiptime/MainActivity.kt
+++ b/app/src/main/java/com/example/tiptime/MainActivity.kt
@@ -1,18 +1,3 @@
-/*
- * 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.
- */
package com.example.tiptime
import android.os.Bundle
@@ -21,31 +6,12 @@ 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
-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.safeDrawingPadding
-import androidx.compose.foundation.layout.statusBarsPadding
-import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Switch
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@@ -55,7 +21,10 @@ 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
+import kotlin.math.roundToInt
+import androidx.compose.foundation.background
+import androidx.compose.ui.graphics.Brush
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -63,10 +32,8 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
TipTimeTheme {
- Surface(
- modifier = Modifier.fillMaxSize(),
- ) {
- TipTimeLayout()
+ Surface(modifier = Modifier.fillMaxSize()) {
+ BmiCalculatorLayout()
}
}
}
@@ -74,71 +41,162 @@ class MainActivity : ComponentActivity() {
}
@Composable
-fun TipTimeLayout() {
- var amountInput by remember { mutableStateOf("") }
- var tipInput by remember { mutableStateOf("") }
- var roundUp by remember { mutableStateOf(false) }
+fun BmiCalculatorLayout() {
+ var heightInput by remember { mutableStateOf("") }
+ var weightInput by remember { mutableStateOf("") }
+ var errorMessage by remember { mutableStateOf("") }
- val BmiHeight = amountInput.toDoubleOrNull() ?: 0.0
- val BmiWeight = tipInput.toDoubleOrNull() ?: 0.0
- val bmi = calculateBMI(BmiHeight, BmiWeight, roundUp)
- val category = calculateBMICategory(BmiHeight, BmiWeight, roundUp)
+ // State untuk hasil BMI
+ var bmiResult by remember { mutableStateOf(null) }
+ var bmiCategory by remember { mutableStateOf("") }
- Column(
+ val height = heightInput.toFloatOrNull() ?: 0f
+ val weight = weightInput.toFloatOrNull() ?: 0f
+ val isValid = validateInput(height, weight)
+
+ Box(
modifier = Modifier
- .statusBarsPadding()
- .padding(horizontal = 40.dp)
+ .fillMaxSize()
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ MaterialTheme.colorScheme.primary.copy(alpha = 0.4f),
+ MaterialTheme.colorScheme.secondary.copy(alpha = 0.2f)
+ )
+ )
+ )
.verticalScroll(rememberScrollState())
- .safeDrawingPadding(),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
+ .padding(horizontal = 24.dp, vertical = 32.dp)
) {
- Text(
- text = stringResource(R.string.calculate_tip),
- 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
- )
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Spacer(modifier = Modifier.height(150.dp))
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_launcher_foreground),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.size(48.dp)
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = "Kalkulator BMI",
+ style = MaterialTheme.typography.headlineMedium.copy(
+ color = MaterialTheme.colorScheme.primary
+ )
+ )
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Card(
+ elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
+ ),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ EditNumberField(
+ label = R.string.height,
+ leadingIcon = R.drawable.number,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number,
+ imeAction = ImeAction.Next
+ ),
+ value = heightInput,
+ onValueChanged = { heightInput = it },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 12.dp)
+ )
+
+ EditNumberField(
+ label = R.string.weight,
+ leadingIcon = R.drawable.number,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number,
+ imeAction = ImeAction.Done
+ ),
+ value = weightInput,
+ onValueChanged = { weightInput = it },
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Button(
+ onClick = {
+ if (!isValid) {
+ errorMessage = "Masukkan tinggi (20β250 cm) dan berat (10β250 kg) yang valid!"
+ bmiResult = null
+ } else {
+ errorMessage = ""
+ bmiResult = calculateBMI(weight, height)
+ bmiCategory = getBMICategory(bmiResult!!)
+ }
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp),
+ elevation = ButtonDefaults.elevatedButtonElevation(8.dp)
+ ) {
+ Text(text = "Hitung BMI", style = MaterialTheme.typography.titleMedium)
+ }
+
+ if (errorMessage.isNotEmpty()) {
+ Text(
+ text = errorMessage,
+ color = MaterialTheme.colorScheme.error,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ }
+
+ // Tampilkan hasil hanya jika sudah ditekan
+ bmiResult?.let { bmi ->
+ val categoryColor = when (bmiCategory) {
+ "Kurus" -> MaterialTheme.colorScheme.tertiaryContainer
+ "Normal" -> MaterialTheme.colorScheme.primaryContainer
+ "Kelebihan Berat" -> MaterialTheme.colorScheme.secondaryContainer
+ else -> MaterialTheme.colorScheme.errorContainer
+ }
+
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 24.dp),
+ colors = CardDefaults.cardColors(containerColor = categoryColor),
+ elevation = CardDefaults.cardElevation(defaultElevation = 10.dp)
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.padding(20.dp)
+ ) {
+ Text(
+ text = "BMI Anda: %.2f".format(bmi),
+ style = MaterialTheme.typography.titleLarge
+ )
+ Text(
+ text = "Kategori: $bmiCategory",
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
+ }
}
}
+
+
+/**
+ * Fungsi untuk membuat TextField input angka
+ */
@Composable
fun EditNumberField(
@StringRes label: Int,
@@ -150,65 +208,47 @@ fun EditNumberField(
) {
TextField(
value = value,
- singleLine = true,
- leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
- modifier = modifier,
onValueChange = onValueChanged,
+ singleLine = true,
+ leadingIcon = { Icon(painter = painterResource(id = leadingIcon), contentDescription = null) },
label = { Text(stringResource(label)) },
- keyboardOptions = keyboardOptions
+ keyboardOptions = keyboardOptions,
+ modifier = modifier
)
}
-@Composable
-fun RoundTheTipRow(
- roundUp: Boolean,
- onRoundUpChanged: (Boolean) -> Unit,
- modifier: Modifier = Modifier
-) {
- Row(
- modifier = modifier.fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(text = stringResource(R.string.use_usc))
- Switch(
- modifier = Modifier
- .fillMaxWidth()
- .wrapContentWidth(Alignment.End),
- checked = roundUp,
- onCheckedChange = onRoundUpChanged
- )
+/**
+ * Menghitung nilai BMI berdasarkan berat (kg) dan tinggi (cm)
+ * Rumus: BMI = berat / (tinggi/100)^2
+ */
+fun calculateBMI(weight: Float, height: Float): Float {
+ if (height <= 0) return 0f
+ return weight / (height / 100).pow(2)
+}
+
+/**
+ * Menentukan kategori BMI
+ */
+fun getBMICategory(bmi: Float): String {
+ return when {
+ bmi < 18.5 -> "Kurus"
+ bmi < 25 -> "Normal"
+ bmi < 30 -> "Kelebihan Berat"
+ else -> "Obesitas"
}
}
/**
- * Calculates the BMI
- *
- * Catatan: tambahkan unit test untuk kalkulasi BMI ini
+ * Validasi input agar nilai masuk akal
*/
-private fun calculateBMI(BmiHeight: Double, BmiWeight: Double = 15.0, roundUp: Boolean): String {
- var bmi = BmiWeight / 100 * BmiHeight
- if (roundUp) {
- bmi = kotlin.math.ceil(bmi)
- }
- return NumberFormat.getNumberInstance().format(bmi)
+fun validateInput(height: Float, weight: Float): Boolean {
+ return height in 20f..250f && weight in 10f..250f
}
-/**
- * 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)
- }
- return NumberFormat.getNumberInstance().format(bmi)
-}
@Preview(showBackground = true)
@Composable
-fun TipTimeLayoutPreview() {
+fun BmiCalculatorPreview() {
TipTimeTheme {
- TipTimeLayout()
+ BmiCalculatorLayout()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/example/tiptime/SplashActivity.kt b/app/src/main/java/com/example/tiptime/SplashActivity.kt
new file mode 100644
index 0000000..b581b87
--- /dev/null
+++ b/app/src/main/java/com/example/tiptime/SplashActivity.kt
@@ -0,0 +1,21 @@
+package com.example.tiptime
+
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.widget.Button
+
+class SplashActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_splash)
+
+ val btnMulai = findViewById