UTS
13
README.md
@ -1,7 +1,18 @@
|
|||||||
Kalkulator BMI
|
Kalkulator BMI
|
||||||
===============
|
===============
|
||||||
|
Langkah-langkah:
|
||||||
|
|
||||||
Silahkan kembangkan aplikasi ini untuk melakukan perhitungan BMI
|
1. Clone Repository Project
|
||||||
|
Saya meng-clone repository project dari GitHub menggunakan Android Studio untuk dijadikan dasar pengerjaan tugas.
|
||||||
|
|
||||||
|
2. Modifikasi Tampilan Aplikasi
|
||||||
|
Saya meminta bantuan ChatGPT dan DeepSeek untuk memberikan saran tampilan aplikasi agar lebih menarik. Setelah itu, saya menyesuaikan tampilan sesuai arahan yang diberikan.
|
||||||
|
|
||||||
|
3. Mengubah Icon Aplikasi (APK)
|
||||||
|
Saya mengganti icon aplikasi melalui fitur Image Asset di Android Studio dengan icon baru sesuai arahan dari ChatGPT.
|
||||||
|
|
||||||
|
4. Review dan Uji Coba
|
||||||
|
Setelah perubahan selesai, saya menjalankan aplikasi untuk memastikan tampilan dan icon sudah berubah dengan baik.
|
||||||
|
|
||||||
Petunjuk lebih detil dapat dibaca di
|
Petunjuk lebih detil dapat dibaca di
|
||||||
https://docs.google.com/document/d/1iGiC0Bg3Bdcd2Maq45TYkCDUkZ5Ql51E/edit?rtpof=true
|
https://docs.google.com/document/d/1iGiC0Bg3Bdcd2Maq45TYkCDUkZ5Ql51E/edit?rtpof=true
|
||||||
|
|||||||
@ -20,9 +20,9 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/app_icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/app_icon_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.TipTime"
|
android:theme="@style/Theme.TipTime"
|
||||||
tools:targetApi="33">
|
tools:targetApi="33">
|
||||||
|
|||||||
BIN
app/src/main/app_icon-playstore.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
@ -54,8 +54,9 @@ 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.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.tiptime.ui.theme.TipTimeTheme
|
import com.example.tiptime.ui.theme.TipTimeTheme
|
||||||
import java.text.NumberFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -75,14 +76,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TipTimeLayout() {
|
fun TipTimeLayout() {
|
||||||
var amountInput by remember { mutableStateOf("") }
|
var heightInput by remember { mutableStateOf("") }
|
||||||
var tipInput by remember { mutableStateOf("") }
|
var weightInput by remember { mutableStateOf("") }
|
||||||
var roundUp by remember { mutableStateOf(false) }
|
var useUSC by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val BmiHeight = amountInput.toDoubleOrNull() ?: 0.0
|
val height = heightInput.toDoubleOrNull() ?: 0.0
|
||||||
val BmiWeight = tipInput.toDoubleOrNull() ?: 0.0
|
val weight = weightInput.toDoubleOrNull() ?: 0.0
|
||||||
val bmi = calculateBMI(BmiHeight, BmiWeight, roundUp)
|
|
||||||
val category = calculateBMICategory(BmiHeight, BmiWeight, roundUp)
|
val bmi = calculateBMI(height, weight, useUSC)
|
||||||
|
val category = calculateBMICategory(bmi)
|
||||||
|
val categoryColor = getCategoryColor(category)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -94,11 +97,14 @@ fun TipTimeLayout() {
|
|||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.calculate_tip),
|
text = "Calculate BMI",
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 16.dp, top = 40.dp)
|
.padding(bottom = 16.dp, top = 40.dp)
|
||||||
.align(alignment = Alignment.Start)
|
.align(alignment = Alignment.Start)
|
||||||
)
|
)
|
||||||
|
|
||||||
EditNumberField(
|
EditNumberField(
|
||||||
label = R.string.height,
|
label = R.string.height,
|
||||||
leadingIcon = R.drawable.number,
|
leadingIcon = R.drawable.number,
|
||||||
@ -106,10 +112,13 @@ fun TipTimeLayout() {
|
|||||||
keyboardType = KeyboardType.Number,
|
keyboardType = KeyboardType.Number,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
value = amountInput,
|
value = heightInput,
|
||||||
onValueChanged = { amountInput = it },
|
onValueChanged = { heightInput = it },
|
||||||
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.padding(bottom = 32.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
EditNumberField(
|
EditNumberField(
|
||||||
label = R.string.weight,
|
label = R.string.weight,
|
||||||
leadingIcon = R.drawable.number,
|
leadingIcon = R.drawable.number,
|
||||||
@ -117,22 +126,30 @@ fun TipTimeLayout() {
|
|||||||
keyboardType = KeyboardType.Number,
|
keyboardType = KeyboardType.Number,
|
||||||
imeAction = ImeAction.Done
|
imeAction = ImeAction.Done
|
||||||
),
|
),
|
||||||
value = tipInput,
|
value = weightInput,
|
||||||
onValueChanged = { tipInput = it },
|
onValueChanged = { weightInput = it },
|
||||||
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.padding(bottom = 32.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
RoundTheTipRow(
|
|
||||||
roundUp = roundUp,
|
RoundTheUnitRow(
|
||||||
onRoundUpChanged = { roundUp = it },
|
useUSC = useUSC,
|
||||||
|
onUnitChanged = { useUSC = it },
|
||||||
modifier = Modifier.padding(bottom = 32.dp)
|
modifier = Modifier.padding(bottom = 32.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.bmi_calculation, bmi),
|
text = "BMI Anda: $bmi",
|
||||||
style = MaterialTheme.typography.displaySmall
|
style = MaterialTheme.typography.displaySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.bmi_category, category),
|
text = "Kategori: $category",
|
||||||
style = MaterialTheme.typography.displaySmall
|
fontSize = 20.sp,
|
||||||
|
color = categoryColor,
|
||||||
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(150.dp))
|
Spacer(modifier = Modifier.height(150.dp))
|
||||||
@ -160,51 +177,75 @@ fun EditNumberField(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoundTheTipRow(
|
fun RoundTheUnitRow(
|
||||||
roundUp: Boolean,
|
useUSC: Boolean,
|
||||||
onRoundUpChanged: (Boolean) -> Unit,
|
onUnitChanged: (Boolean) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.use_usc))
|
Text(
|
||||||
|
text = "Gunakan Unit (Metric/USC)",
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
Switch(
|
Switch(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentWidth(Alignment.End),
|
.wrapContentWidth(Alignment.End),
|
||||||
checked = roundUp,
|
checked = useUSC,
|
||||||
onCheckedChange = onRoundUpChanged
|
onCheckedChange = onUnitChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the BMI
|
* Menghitung BMI berdasarkan rumus yang digunakan
|
||||||
*
|
|
||||||
* Catatan: tambahkan unit test untuk kalkulasi BMI ini
|
|
||||||
*/
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Calculates the BMI Category
|
|
||||||
*
|
|
||||||
* Catatan: tambahkan unit test untuk kalkulasi BMI ini
|
|
||||||
*/
|
*/
|
||||||
|
internal fun calculateBMI(height: Double, weight: Double, useUSC: Boolean): String {
|
||||||
|
if (height == 0.0 || weight == 0.0) return "0.0"
|
||||||
|
|
||||||
private fun calculateBMICategory(BmiHeight: Double, BmiWeight: Double = 15.0, roundUp: Boolean): String {
|
val bmi = if (useUSC) {
|
||||||
var bmi = BmiWeight / 100 * BmiHeight
|
// Rumus USC: 703 * (berat (lbs) / (tinggi (in)^2))
|
||||||
if (roundUp) {
|
703 * (weight / (height * height))
|
||||||
bmi = kotlin.math.ceil(bmi)
|
} else {
|
||||||
|
// Rumus Metric: berat (kg) / (tinggi (m)^2)
|
||||||
|
val heightInMeter = height / 100
|
||||||
|
weight / (heightInMeter * heightInMeter)
|
||||||
}
|
}
|
||||||
return NumberFormat.getNumberInstance().format(bmi)
|
|
||||||
|
return DecimalFormat("#.0").format(bmi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menentukan kategori BMI
|
||||||
|
*/
|
||||||
|
internal fun calculateBMICategory(bmiString: String): String {
|
||||||
|
val bmi = bmiString.toDoubleOrNull() ?: 0.0
|
||||||
|
return when {
|
||||||
|
bmi < 18.5 -> "Underweight"
|
||||||
|
bmi < 25.0 -> "Normal weight"
|
||||||
|
bmi < 30.0 -> "Overweight"
|
||||||
|
bmi >= 30.0 -> "Obese"
|
||||||
|
else -> "Tidak diketahui"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memberikan warna berdasarkan kategori BMI - menggunakan warna dari tema
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun getCategoryColor(category: String): androidx.compose.ui.graphics.Color {
|
||||||
|
return when (category) {
|
||||||
|
"Underweight" -> androidx.compose.ui.graphics.Color(0xFF2196F3) // Biru
|
||||||
|
"Normal weight" -> androidx.compose.ui.graphics.Color(0xFF4CAF50) // Hijau
|
||||||
|
"Overweight" -> androidx.compose.ui.graphics.Color(0xFFFF9800) // Orange
|
||||||
|
"Obese" -> androidx.compose.ui.graphics.Color(0xFFF44336) // Merah
|
||||||
|
else -> MaterialTheme.colorScheme.onSurface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun TipTimeLayoutPreview() {
|
fun TipTimeLayoutPreview() {
|
||||||
|
|||||||
5
app/src/main/res/mipmap-anydpi-v26/app_icon.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@mipmap/app_icon_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/app_icon_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
5
app/src/main/res/mipmap-anydpi-v26/app_icon_round.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@mipmap/app_icon_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/app_icon_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
BIN
app/src/main/res/mipmap-hdpi/app_icon.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-hdpi/app_icon_background.webp
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
app/src/main/res/mipmap-hdpi/app_icon_foreground.webp
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
app/src/main/res/mipmap-hdpi/app_icon_round.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/main/res/mipmap-mdpi/app_icon.webp
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/mipmap-mdpi/app_icon_background.webp
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/main/res/mipmap-mdpi/app_icon_foreground.webp
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
app/src/main/res/mipmap-mdpi/app_icon_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/app_icon.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/mipmap-xhdpi/app_icon_background.webp
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
app/src/main/res/mipmap-xhdpi/app_icon_foreground.webp
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
app/src/main/res/mipmap-xhdpi/app_icon_round.webp
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/app_icon.webp
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/app_icon_background.webp
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/app_icon_foreground.webp
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/app_icon_round.webp
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/app_icon.webp
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/app_icon_background.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/app_icon_foreground.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/app_icon_round.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |