Compare commits

..

No commits in common. "7e4ab1c28d6c322dfbeaf0e8b5708a80a9c79113" and "076428c67eb33d47864b8130b02d090ac646f991" have entirely different histories.

9 changed files with 190 additions and 718 deletions

View File

@ -1,63 +1,24 @@
Kalkulator BMI
===============
//Satrio Putra Wardani
//UTS BMI
Tip Time - Solution Code
=================================
Silahkan kembangkan aplikasi ini untuk melakukan perhitungan BMI
Solution code for the [Android Basics with Compose](https://developer.android.com/courses/android-basics-compose/course): Tip Time app.
Starter dimodifikasi dan terinspirasi dari:
https://developer.android.com/codelabs/basic-android-compose-calculate-tip#0
# 🧮 BMI Calculator (Metrik & Imperial)
Sebuah aplikasi modern dan interaktif untuk menghitung **Body Mass Index (BMI)** menggunakan dua sistem satuan:
🌍 **Metrik (kg, m)** dan 🇺🇸 **Imperial (lbs, inch)**.
Aplikasi ini membantu kamu mengetahui apakah berat badanmu tergolong **kurus**, **ideal**, **berlebih**, atau **obesitas** berdasarkan standar kesehatan internasional (WHO).
---
## ✨ Fitur Utama
✅ Hitung BMI dengan **dua pilihan sistem satuan**:
- **Metrik** → kilogram (kg) & meter (m)
- **Imperial** → pound (lbs) & inch (in)
✅ Menampilkan hasil **BMI secara otomatis**
✅ Menentukan **kategori berat badan** sesuai standar WHO
✅ Antarmuka **sederhana, responsif, dan mudah digunakan**
✅ Dapat dijalankan di **desktop maupun mobile**
✅ Dikembangkan dengan bantuan **AI cerdas (ChatGPT & Claude)**
---
Introduction
------------
The Tip Time app contains various UI elements for calculating a tip,
teaching about user input, and State in Compose.
Pre-requisites
--------------
* Experience with Kotlin syntax.
* How to create and run a project in Android Studio.
## 🧠 Kategori BMI
| Kategori | Rentang Nilai BMI |
|------------------------------|------------------|
| Kurang / Underweight | < 18.5 |
| Normal / Ideal | 18.5 24.9 |
| Kelebihan berat badan / Overweight | 25.0 29.9 |
| Obesitas / Obese | ≥ 30.0 |
---
## 🚀 Cara Menggunakan
1. Pilih sistem satuan: **Metrik** atau **Imperial**
2. Masukkan berat badan dan tinggi badan sesuai sistem satuan
3. Klik tombol **Hitung / Calculate**
4. Lihat hasil **BMI** dan kategori kesehatannya
---
## 💻 Contoh Penggunaan
### 🌍 **Metrik**
Aplikasi Ini Di Bantu Oleh
-ChatGpt
-Claude Ai
Satrio Putra Wardani 202310715307
Getting Started
---------------
1. Install Android Studio, if you don't already have it.
2. Download the sample.
3. Import the sample into Android Studio.
4. Build and run the sample.

View File

@ -17,7 +17,6 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
}
android {
@ -46,15 +45,18 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = rootProject.extra["compose_compiler_version"].toString()
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
@ -65,7 +67,7 @@ android {
dependencies {
implementation(platform("androidx.compose:compose-bom:2024.12.01"))
implementation(platform("androidx.compose:compose-bom:2024.11.00"))
implementation("androidx.activity:activity-compose:1.9.3")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
@ -76,7 +78,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.12.01"))
androidTestImplementation(platform("androidx.compose:compose-bom:2024.11.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("androidx.test.ext:junit:1.2.1")

View File

@ -1,3 +1,18 @@
/*
* 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
@ -5,42 +20,42 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.DrawableRes
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
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.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
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.*
import androidx.compose.runtime.*
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
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.unit.dp
import androidx.compose.ui.unit.sp
import com.example.tiptime.ui.theme.TipTimeTheme
import kotlin.math.pow
import java.text.DecimalFormat
// Warna tema klasik
private val ClassicGold = Color(0xFFD4AF37)
private val ClassicCream = Color(0xFFFFF8DC)
private val ClassicBrown = Color(0xFF8B4513)
private val ClassicDarkBrown = Color(0xFF654321)
private val ClassicBeige = Color(0xFFF5F5DC)
import java.text.NumberFormat
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -48,14 +63,10 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
TipTimeTheme {
Surface(modifier = Modifier.fillMaxSize()) {
var showWelcome by remember { mutableStateOf(true) }
if (showWelcome) {
WelcomeScreen(onContinue = { showWelcome = false })
} else {
BmiCalculatorLayout(onBackPressed = { showWelcome = true })
}
Surface(
modifier = Modifier.fillMaxSize(),
) {
TipTimeLayout()
}
}
}
@ -63,652 +74,123 @@ class MainActivity : ComponentActivity() {
}
@Composable
fun WelcomeScreen(onContinue: () -> Unit) {
var visible by remember { mutableStateOf(false) }
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
var tipInput by remember { mutableStateOf("") }
var roundUp by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
visible = true
}
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount, tipPercent, roundUp)
Box(
Column(
modifier = Modifier
.fillMaxSize()
.background(
Brush.verticalGradient(
colors = listOf(
ClassicDarkBrown,
ClassicBrown,
ClassicBeige
)
)
)
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Column(
Text(
text = stringResource(R.string.calculate_tip),
modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween
) {
// Spacer untuk centering
Spacer(modifier = Modifier.height(60.dp))
// Content utama
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(1000)) +
scaleIn(initialScale = 0.8f, animationSpec = tween(1000))
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
// Icon/Logo
Card(
modifier = Modifier
.size(140.dp)
.shadow(16.dp, RoundedCornerShape(70.dp)),
shape = RoundedCornerShape(70.dp),
colors = CardDefaults.cardColors(
containerColor = ClassicGold
),
border = BorderStroke(4.dp, ClassicCream)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Text(
text = "💪",
fontSize = 72.sp
)
}
}
Spacer(modifier = Modifier.height(40.dp))
// Ornamen atas
Text(
text = "✦ ═══════════════ ✦",
color = ClassicGold,
fontSize = 18.sp,
modifier = Modifier.padding(bottom = 20.dp)
)
// Judul utama
Text(
text = "KALKULATOR",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = ClassicGold,
letterSpacing = 5.sp
)
Text(
text = "BMI Satrio Putra Wardani",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = ClassicCream,
letterSpacing = 3.sp,
modifier = Modifier.padding(vertical = 12.dp)
)
// Ornamen bawah
Text(
text = "✦ ═══════════════ ✦",
color = ClassicGold,
fontSize = 18.sp,
modifier = Modifier.padding(bottom = 32.dp)
)
// Deskripsi
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = ClassicCream.copy(alpha = 0.15f)
),
border = BorderStroke(1.5.dp, ClassicGold.copy(alpha = 0.4f))
) {
Column(
modifier = Modifier.padding(28.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Hitung Indeks Massa Tubuh Anda",
fontSize = 19.sp,
fontWeight = FontWeight.SemiBold,
color = ClassicCream,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = "Ketahui status kesehatan Anda dengan perhitungan BMI yang akurat dan mudah",
fontSize = 15.sp,
color = ClassicCream.copy(alpha = 0.85f),
textAlign = TextAlign.Center,
lineHeight = 22.sp
)
}
}
}
}
// Tombol dan footer
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(1000, delayMillis = 500)) +
slideInVertically(
initialOffsetY = { 50 },
animationSpec = tween(1000, delayMillis = 500)
)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(bottom = 8.dp)
) {
Button(
onClick = onContinue,
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.shadow(12.dp, RoundedCornerShape(30.dp)),
shape = RoundedCornerShape(30.dp),
colors = ButtonDefaults.buttonColors(
containerColor = ClassicGold,
contentColor = ClassicDarkBrown
),
border = BorderStroke(2.5.dp, ClassicCream)
) {
Text(
text = "MULAI PERHITUNGAN",
fontSize = 19.sp,
fontWeight = FontWeight.Bold,
letterSpacing = 1.5.sp
)
}
Spacer(modifier = Modifier.height(28.dp))
// Divider dekoratif
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 32.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.height(1.dp)
.background(ClassicCream.copy(alpha = 0.3f))
)
Text(
text = "",
color = ClassicGold.copy(alpha = 0.6f),
fontSize = 16.sp
)
Box(
modifier = Modifier
.weight(1f)
.height(1.dp)
.background(ClassicCream.copy(alpha = 0.3f))
)
}
Spacer(modifier = Modifier.height(20.dp))
Text(
text = "Universitas Bhayangkara Jakarta Raya",
fontSize = 13.sp,
color = ClassicDarkBrown.copy(alpha = 0.5f),
letterSpacing = 1.sp
)
Text(
text = "SATRIO PUTRA WARDANI",
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
color = ClassicGold.copy(alpha = 0.8f),
letterSpacing = 2.sp,
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
.padding(bottom = 16.dp, top = 40.dp)
.align(alignment = Alignment.Start)
)
EditNumberField(
label = R.string.bill_amount,
leadingIcon = R.drawable.money,
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.how_was_the_service,
leadingIcon = R.drawable.percent,
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.tip_amount, tip),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.height(150.dp))
}
}
@Composable
fun BmiCalculatorLayout(onBackPressed: () -> Unit) {
var heightInput by remember { mutableStateOf("") }
var weightInput by remember { mutableStateOf("") }
var useImperial by remember { mutableStateOf(false) }
val height = heightInput.toDoubleOrNull() ?: 0.0
val weight = weightInput.toDoubleOrNull() ?: 0.0
val bmi = calculateBMI(height, weight, useImperial)
val category = getBMICategory(bmi)
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.verticalGradient(
colors = listOf(
ClassicCream,
ClassicBeige
)
)
)
) {
Column(
modifier = Modifier
.statusBarsPadding()
.padding(20.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Tombol Back
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = onBackPressed,
modifier = Modifier
.shadow(4.dp, RoundedCornerShape(12.dp))
.background(ClassicGold, RoundedCornerShape(12.dp))
.size(48.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Kembali",
tint = ClassicDarkBrown
)
}
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "Kembali ke Beranda",
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = ClassicDarkBrown
)
}
// Header dengan ornamen klasik
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
.shadow(8.dp, RoundedCornerShape(16.dp)),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = ClassicDarkBrown
),
border = BorderStroke(2.dp, ClassicGold)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(24.dp)
) {
Text(
text = "═══════════════",
color = ClassicGold,
fontSize = 20.sp
)
Text(
text = "KALKULATOR BMI",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = ClassicGold,
letterSpacing = 2.sp,
modifier = Modifier.padding(vertical = 8.dp)
)
Text(
text = "═══════════════",
color = ClassicGold,
fontSize = 20.sp
)
Text(
text = "Body Mass Index Calculator",
fontSize = 14.sp,
color = ClassicCream,
fontWeight = FontWeight.Light,
modifier = Modifier.padding(top = 8.dp)
)
}
}
// Form Input Card
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.shadow(6.dp, RoundedCornerShape(12.dp)),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = Color.White
),
border = BorderStroke(1.dp, ClassicGold)
) {
Column(
modifier = Modifier.padding(20.dp)
) {
Text(
text = "Data Pengukuran",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = ClassicDarkBrown,
modifier = Modifier.padding(bottom = 16.dp)
)
ClassicTextField(
label = if (useImperial) "Tinggi (inch)" else "Tinggi (cm)",
leadingIcon = R.drawable.number,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
value = heightInput,
onValueChanged = { heightInput = it },
modifier = Modifier
.padding(bottom = 16.dp)
.fillMaxWidth()
)
ClassicTextField(
label = if (useImperial) "Berat (lbs)" else "Berat (kg)",
leadingIcon = R.drawable.number,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
value = weightInput,
onValueChanged = { weightInput = it },
modifier = Modifier
.padding(bottom = 16.dp)
.fillMaxWidth()
)
HorizontalDivider(
color = ClassicGold,
thickness = 1.dp,
modifier = Modifier.padding(vertical = 8.dp)
)
ClassicUnitSwitch(
useImperial = useImperial,
onUnitChange = { useImperial = it }
)
}
}
// Result Card
if (height > 0 && weight > 0) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.shadow(8.dp, RoundedCornerShape(12.dp)),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = ClassicDarkBrown
),
border = BorderStroke(2.dp, ClassicGold)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(28.dp)
) {
Text(
text = "— Hasil Perhitungan —",
fontSize = 16.sp,
color = ClassicGold,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = "INDEKS MASSA TUBUH",
fontSize = 14.sp,
color = ClassicCream,
letterSpacing = 1.sp,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = DecimalFormat("#.##").format(bmi),
fontSize = 56.sp,
fontWeight = FontWeight.Bold,
color = ClassicGold,
modifier = Modifier.padding(vertical = 8.dp)
)
Box(
modifier = Modifier
.background(
color = getCategoryColor(category),
shape = RoundedCornerShape(24.dp)
)
.padding(horizontal = 24.dp, vertical = 12.dp)
) {
Text(
text = category.uppercase(),
fontSize = 20.sp,
color = Color.White,
fontWeight = FontWeight.Bold,
letterSpacing = 1.sp
)
}
Text(
text = getCategoryDescription(category),
fontSize = 13.sp,
color = ClassicCream,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 16.dp)
)
}
}
// Info Card
BMICategoryInfo()
}
Spacer(modifier = Modifier.height(40.dp))
}
}
}
@Composable
fun ClassicTextField(
label: String,
fun EditNumberField(
@StringRes label: Int,
@DrawableRes leadingIcon: Int,
keyboardOptions: KeyboardOptions,
value: String,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
) {
OutlinedTextField(
TextField(
value = value,
onValueChange = onValueChanged,
label = {
Text(
label,
color = ClassicBrown,
fontWeight = FontWeight.Medium
)
},
singleLine = true,
leadingIcon = {
Icon(
painter = painterResource(id = leadingIcon),
contentDescription = null,
tint = ClassicGold
)
},
keyboardOptions = keyboardOptions,
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
modifier = modifier,
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = ClassicGold,
unfocusedBorderColor = ClassicBrown.copy(alpha = 0.5f),
focusedTextColor = ClassicDarkBrown,
unfocusedTextColor = ClassicDarkBrown
),
shape = RoundedCornerShape(8.dp)
onValueChange = onValueChanged,
label = { Text(stringResource(label)) },
keyboardOptions = keyboardOptions
)
}
@Composable
fun ClassicUnitSwitch(
useImperial: Boolean,
onUnitChange: (Boolean) -> Unit
fun RoundTheTipRow(
roundUp: Boolean,
onRoundUpChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
"Satuan Imperial (US)",
color = ClassicDarkBrown,
fontWeight = FontWeight.Medium
)
Switch(
checked = useImperial,
onCheckedChange = onUnitChange,
colors = SwitchDefaults.colors(
checkedThumbColor = ClassicGold,
checkedTrackColor = ClassicGold.copy(alpha = 0.5f),
uncheckedThumbColor = ClassicBrown,
uncheckedTrackColor = ClassicBrown.copy(alpha = 0.3f)
)
)
}
}
@Composable
fun BMICategoryInfo() {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.shadow(4.dp, RoundedCornerShape(12.dp)),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = Color.White
),
border = BorderStroke(1.dp, ClassicGold.copy(alpha = 0.3f))
) {
Column(
modifier = Modifier.padding(20.dp)
) {
Text(
text = "Kategori BMI",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = ClassicDarkBrown,
modifier = Modifier.padding(bottom = 12.dp)
)
CategoryRow("Kurus", "< 18.5", Color(0xFF4A90E2))
CategoryRow("Normal", "18.5 - 24.9", Color(0xFF50C878))
CategoryRow("Gemuk", "25.0 - 29.9", Color(0xFFFF9500))
CategoryRow("Obesitas", "≥ 30.0", Color(0xFFE74C3C))
}
}
}
@Composable
fun CategoryRow(category: String, range: String, color: Color) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 6.dp),
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Box(
Text(text = stringResource(R.string.round_up_tip))
Switch(
modifier = Modifier
.size(12.dp)
.background(color, RoundedCornerShape(2.dp))
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = category,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = ClassicDarkBrown,
modifier = Modifier.weight(1f)
)
Text(
text = range,
fontSize = 14.sp,
color = ClassicBrown,
fontWeight = FontWeight.Normal
.fillMaxWidth()
.wrapContentWidth(Alignment.End),
checked = roundUp,
onCheckedChange = onRoundUpChanged
)
}
}
fun getCategoryColor(category: String): Color {
return when (category) {
"Kurus" -> Color(0xFF4A90E2)
"Normal" -> Color(0xFF50C878)
"Gemuk" -> Color(0xFFFF9500)
"Obesitas" -> Color(0xFFE74C3C)
else -> ClassicBrown
}
}
fun getCategoryDescription(category: String): String {
return when (category) {
"Kurus" -> "Berat badan Anda kurang dari ideal"
"Normal" -> "Berat badan Anda ideal dan sehat"
"Gemuk" -> "Berat badan Anda melebihi ideal"
"Obesitas" -> "Konsultasikan dengan dokter Anda"
else -> ""
}
}
fun calculateBMI(height: Double, weight: Double, useImperial: Boolean): Double {
if (height <= 0 || weight <= 0) return 0.0
return if (useImperial) {
703 * weight / height.pow(2)
} else {
weight / (height / 100).pow(2)
}
}
fun getBMICategory(bmi: Double): String {
return when {
bmi == 0.0 -> "-"
bmi < 18.5 -> "Kurus"
bmi < 25 -> "Normal"
bmi < 30 -> "Gemuk"
else -> "Obesitas"
/**
* Calculates the tip based on the user input and format the tip amount
* according to the local currency.
* Example would be "$10.00".
*/
private fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
var tip = tipPercent / 100 * amount
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
return NumberFormat.getCurrencyInstance().format(tip)
}
@Preview(showBackground = true)
@Composable
fun WelcomeScreenPreview() {
fun TipTimeLayoutPreview() {
TipTimeTheme {
WelcomeScreen(onContinue = {})
TipTimeLayout()
}
}
@Preview(showBackground = true)
@Composable
fun BmiCalculatorPreview() {
TipTimeTheme {
BmiCalculatorLayout(onBackPressed = {})
}
}

View File

@ -0,0 +1,23 @@
<!--
~ 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.
-->
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7.5,11C9.43,11 11,9.43 11,7.5S9.43,4 7.5,4S4,5.57 4,7.5S5.57,11 7.5,11zM7.5,6C8.33,6 9,6.67 9,7.5S8.33,9 7.5,9S6,8.33 6,7.5S6.67,6 7.5,6z"/>
<path android:fillColor="@android:color/white" android:pathData="M4.0025,18.5831l14.5875,-14.5875l1.4142,1.4142l-14.5875,14.5875z"/>
<path android:fillColor="@android:color/white" android:pathData="M16.5,13c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S18.43,13 16.5,13zM16.5,18c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5S17.33,18 16.5,18z"/>
</vector>

View File

@ -15,11 +15,10 @@
~ 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>
<string name="app_name">Tip Time</string>
<string name="calculate_tip">Calculate Tip</string>
<string name="bill_amount">Bill Amount</string>
<string name="how_was_the_service">Tip Percentage</string>
<string name="round_up_tip">Round up tip?</string>
<string name="tip_amount">Tip Amount: %s</string>
</resources>

View File

@ -14,10 +14,14 @@
* limitations under the License.
*/
buildscript {
extra.apply {
set("compose_compiler_version", "1.5.3")
}
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.13.0" apply false
id("com.android.library") version "8.13.0" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false
id("com.android.application") version "8.7.3" apply false
id("com.android.library") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "1.9.10" apply false
}

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

3
gradlew vendored
View File

@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum