Compare commits

..

10 Commits

Author SHA1 Message Date
nuryuda
7e4ab1c28d UTS 2025-11-07 17:06:44 +07:00
nuryuda
b88d767571 UTS 2025-11-07 17:02:27 +07:00
099c35f19a Update README.md 2025-11-06 12:04:44 +07:00
0ee43c2e9a First update calculation 2025-11-06 11:34:31 +07:00
7053fa6573 First update labels 2025-11-06 11:10:35 +07:00
51c9a8e5ff First commit 2025-11-06 09:58:07 +07:00
renovate[bot]
b029b3dd10
Update all dependencies 8.7.3 to v8.8.0 (#271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-10 05:25:39 +00:00
renovate[bot]
90545a4a4d
Update dependency gradle 8.11.1 to v8.12 (#263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-21 03:18:17 +00:00
renovate[bot]
e075e6ded8
Update dependency androidx.compose:compose-bom 2024.11.00 to v2024.12.01 (#260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 03:31:35 +00:00
Tomáš Mlynarič
7db6c366a3 Update to kotlin 2.1.0 2024-12-09 17:16:15 +01:00
9 changed files with 720 additions and 192 deletions

View File

@ -1,24 +1,63 @@
Tip Time - Solution Code Kalkulator BMI
================================= ===============
//Satrio Putra Wardani
//UTS BMI
Solution code for the [Android Basics with Compose](https://developer.android.com/courses/android-basics-compose/course): Tip Time app. Silahkan kembangkan aplikasi ini untuk melakukan perhitungan BMI
Introduction Starter dimodifikasi dan terinspirasi dari:
------------ https://developer.android.com/codelabs/basic-android-compose-calculate-tip#0
The Tip Time app contains various UI elements for calculating a tip, # 🧮 BMI Calculator (Metrik & Imperial)
teaching about user input, and State in Compose.
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)**
---
Pre-requisites
--------------
* Experience with Kotlin syntax.
* How to create and run a project in Android Studio.
## 🧠 Kategori BMI
Getting Started | Kategori | Rentang Nilai BMI |
--------------- |------------------------------|------------------|
1. Install Android Studio, if you don't already have it. | Kurang / Underweight | < 18.5 |
2. Download the sample. | Normal / Ideal | 18.5 24.9 |
3. Import the sample into Android Studio. | Kelebihan berat badan / Overweight | 25.0 29.9 |
4. Build and run the sample. | 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

View File

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

View File

@ -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 package com.example.tiptime
import android.os.Bundle import android.os.Bundle
@ -20,42 +5,42 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent 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.compose.animation.*
import androidx.compose.foundation.layout.Arrangement import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.*
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.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
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.Icon import androidx.compose.material.icons.Icons
import androidx.compose.material3.MaterialTheme import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Surface import androidx.compose.material3.*
import androidx.compose.material3.Switch import androidx.compose.runtime.*
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.Alignment
import androidx.compose.ui.Modifier 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.res.painterResource
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 androidx.compose.ui.unit.sp
import com.example.tiptime.ui.theme.TipTimeTheme import com.example.tiptime.ui.theme.TipTimeTheme
import java.text.NumberFormat 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)
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -63,10 +48,14 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
TipTimeTheme { TipTimeTheme {
Surface( Surface(modifier = Modifier.fillMaxSize()) {
modifier = Modifier.fillMaxSize(), var showWelcome by remember { mutableStateOf(true) }
) {
TipTimeLayout() if (showWelcome) {
WelcomeScreen(onContinue = { showWelcome = false })
} else {
BmiCalculatorLayout(onBackPressed = { showWelcome = true })
}
} }
} }
} }
@ -74,123 +63,652 @@ class MainActivity : ComponentActivity() {
} }
@Composable @Composable
fun TipTimeLayout() { fun WelcomeScreen(onContinue: () -> Unit) {
var amountInput by remember { mutableStateOf("") } var visible by remember { mutableStateOf(false) }
var tipInput by remember { mutableStateOf("") }
var roundUp by remember { mutableStateOf(false) }
val amount = amountInput.toDoubleOrNull() ?: 0.0 LaunchedEffect(Unit) {
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0 visible = true
val tip = calculateTip(amount, tipPercent, roundUp) }
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.verticalGradient(
colors = listOf(
ClassicDarkBrown,
ClassicBrown,
ClassicBeige
)
)
)
) {
Column(
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)
)
}
}
}
}
}
@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( Column(
modifier = Modifier modifier = Modifier
.statusBarsPadding() .statusBarsPadding()
.padding(horizontal = 40.dp) .padding(20.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState()),
.safeDrawingPadding(), 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, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center modifier = Modifier.padding(24.dp)
) { ) {
Text( Text(
text = stringResource(R.string.calculate_tip), text = "═══════════════",
modifier = Modifier color = ClassicGold,
.padding(bottom = 16.dp, top = 40.dp) fontSize = 20.sp
.align(alignment = Alignment.Start)
) )
EditNumberField( Text(
label = R.string.bill_amount, text = "KALKULATOR BMI",
leadingIcon = R.drawable.money, 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( keyboardOptions = KeyboardOptions.Default.copy(
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 = 16.dp)
.fillMaxWidth()
) )
EditNumberField(
label = R.string.how_was_the_service, ClassicTextField(
leadingIcon = R.drawable.percent, label = if (useImperial) "Berat (lbs)" else "Berat (kg)",
leadingIcon = R.drawable.number,
keyboardOptions = KeyboardOptions.Default.copy( keyboardOptions = KeyboardOptions.Default.copy(
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 = 16.dp)
.fillMaxWidth()
) )
RoundTheTipRow(
roundUp = roundUp, HorizontalDivider(
onRoundUpChanged = { roundUp = it }, color = ClassicGold,
modifier = Modifier.padding(bottom = 32.dp) 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(
text = stringResource(R.string.tip_amount, tip), text = "— Hasil Perhitungan —",
style = MaterialTheme.typography.displaySmall fontSize = 16.sp,
color = ClassicGold,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(bottom = 16.dp)
) )
Spacer(modifier = Modifier.height(150.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 @Composable
fun EditNumberField( fun ClassicTextField(
@StringRes label: Int, label: String,
@DrawableRes leadingIcon: Int, @DrawableRes leadingIcon: Int,
keyboardOptions: KeyboardOptions, keyboardOptions: KeyboardOptions,
value: String, value: String,
onValueChanged: (String) -> Unit, onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
TextField( OutlinedTextField(
value = value, value = value,
singleLine = true,
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
modifier = modifier,
onValueChange = onValueChanged, onValueChange = onValueChanged,
label = { Text(stringResource(label)) }, label = {
keyboardOptions = keyboardOptions Text(
label,
color = ClassicBrown,
fontWeight = FontWeight.Medium
)
},
singleLine = true,
leadingIcon = {
Icon(
painter = painterResource(id = leadingIcon),
contentDescription = null,
tint = ClassicGold
)
},
keyboardOptions = keyboardOptions,
modifier = modifier,
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = ClassicGold,
unfocusedBorderColor = ClassicBrown.copy(alpha = 0.5f),
focusedTextColor = ClassicDarkBrown,
unfocusedTextColor = ClassicDarkBrown
),
shape = RoundedCornerShape(8.dp)
) )
} }
@Composable @Composable
fun RoundTheTipRow( fun ClassicUnitSwitch(
roundUp: Boolean, useImperial: Boolean,
onRoundUpChanged: (Boolean) -> Unit, onUnitChange: (Boolean) -> Unit
modifier: Modifier = Modifier
) { ) {
Row( Row(
modifier = modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text(text = stringResource(R.string.round_up_tip)) Text(
"Satuan Imperial (US)",
color = ClassicDarkBrown,
fontWeight = FontWeight.Medium
)
Switch( Switch(
modifier = Modifier checked = useImperial,
.fillMaxWidth() onCheckedChange = onUnitChange,
.wrapContentWidth(Alignment.End), colors = SwitchDefaults.colors(
checked = roundUp, checkedThumbColor = ClassicGold,
onCheckedChange = onRoundUpChanged checkedTrackColor = ClassicGold.copy(alpha = 0.5f),
uncheckedThumbColor = ClassicBrown,
uncheckedTrackColor = ClassicBrown.copy(alpha = 0.3f)
)
) )
} }
} }
/** @Composable
* Calculates the tip based on the user input and format the tip amount fun BMICategoryInfo() {
* according to the local currency. Card(
* Example would be "$10.00". modifier = Modifier
*/ .fillMaxWidth()
private fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String { .padding(vertical = 8.dp)
var tip = tipPercent / 100 * amount .shadow(4.dp, RoundedCornerShape(12.dp)),
if (roundUp) { shape = RoundedCornerShape(12.dp),
tip = kotlin.math.ceil(tip) 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),
verticalAlignment = Alignment.CenterVertically
) {
Box(
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
)
}
}
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"
} }
return NumberFormat.getCurrencyInstance().format(tip)
} }
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun TipTimeLayoutPreview() { fun WelcomeScreenPreview() {
TipTimeTheme { TipTimeTheme {
TipTimeLayout() WelcomeScreen(onContinue = {})
}
}
@Preview(showBackground = true)
@Composable
fun BmiCalculatorPreview() {
TipTimeTheme {
BmiCalculatorLayout(onBackPressed = {})
} }
} }

View File

@ -1,23 +0,0 @@
<!--
~ 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,10 +15,11 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<resources> <resources>
<string name="app_name">Tip Time</string> <string name="app_name">BMI Calculator</string>
<string name="calculate_tip">Calculate Tip</string> <string name="calculate_tip">Calculate BMI</string>
<string name="bill_amount">Bill Amount</string> <string name="height">Tinggi Badan</string>
<string name="how_was_the_service">Tip Percentage</string> <string name="weight">Berat Badan</string>
<string name="round_up_tip">Round up tip?</string> <string name="use_usc">Gunakan Unit USC (lbs/in)?</string>
<string name="tip_amount">Tip Amount: %s</string> <string name="bmi_calculation">BMI Anda: %s</string>
<string name="bmi_category">Kategori: %s</string>
</resources> </resources>

View File

@ -14,14 +14,10 @@
* limitations under the License. * 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. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id("com.android.application") version "8.7.3" apply false id("com.android.application") version "8.13.0" apply false
id("com.android.library") version "8.7.3" apply false id("com.android.library") version "8.13.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.10" 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
} }

View File

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

3
gradlew vendored
View File

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