Compare commits

...

10 Commits

8 changed files with 376 additions and 223 deletions

102
README.md
View File

@ -1,24 +1,92 @@
Tip Time - Solution Code # Kalkulator BMI Modern
=================================
Solution code for the [Android Basics with Compose](https://developer.android.com/courses/android-basics-compose/course): Tip Time app. Sebuah aplikasi Android modern dan elegan untuk menghitung Indeks Massa Tubuh (BMI), dibangun sepenuhnya menggunakan Jetpack Compose. Proyek ini dikembangkan oleh **Fazri Abdurrahman (202310715082)** dengan kolaborasi intensif bersama AI (Gemini) untuk menerapkan fungsionalitas canggih, desain yang menarik, dan praktik pengembangan modern.
---
Introduction ## 📖 Daftar Isi
------------
The Tip Time app contains various UI elements for calculating a tip,
teaching about user input, and State in Compose.
- [Fitur Utama](#-fitur-utama)
- [Tangkapan Layar](#-tangkapan-layar)
- [Teknologi yang Digunakan](#-teknologi-yang-digunakan)
- [Instalasi & Penggunaan](#-instalasi--penggunaan)
- [Proses Pengembangan dengan AI](#-proses-pengembangan-dengan-ai)
- [Rencana Pengembangan](#-rencana-pengembangan)
- [Lisensi](#-lisensi)
Pre-requisites ---
--------------
* Experience with Kotlin syntax.
* How to create and run a project in Android Studio.
## ✨ Fitur Utama
Getting Started - **Input Data Intuitif:** Form input yang jelas untuk **Tinggi (cm)**, **Berat (kg)**, dan **Umur**.
--------------- - **Visualisasi BMI Real-time:** Hasil BMI ditampilkan secara dinamis melalui **Gauge Chart (Speedometer)** yang interaktif, memberikan umpan balik visual yang langsung dipahami.
1. Install Android Studio, if you don't already have it. - **Analisis Hasil Komprehensif:**
2. Download the sample. - Nilai BMI akurat beserta **kategori** (Kurus, Normal, Gemuk, Obesitas).
3. Import the sample into Android Studio. - Informasi **rentang berat badan ideal** sesuai tinggi badan.
4. Build and run the sample. - Kalkulasi metrik tambahan seperti **BMI Prime** dan **Ponderal Index**.
- **Desain Modern & Responsif:** Antarmuka pengguna yang bersih dengan palet warna custom (hijau army & coklat) yang diimplementasikan menggunakan **Material 3 Theming**.
---
## 📸 Tangkapan Layar
*(Sangat disarankan untuk menambahkan gambar pratinjau aplikasi Anda di sini untuk menunjukkan hasil akhirnya. Ini memberikan kesan pertama yang kuat.)*
---
## 🛠️ Teknologi yang Digunakan
Proyek ini dibangun menggunakan tumpukan teknologi Android modern:
- **Bahasa Pemrograman:** **Kotlin** - Bahasa utama untuk pengembangan Android, dipilih karena keringkasan, keamanan, dan fitur-fitur modernnya.
- **UI Toolkit:** **Jetpack Compose** - Membangun seluruh UI secara deklaratif, memungkinkan pengembangan yang lebih cepat dan kode yang lebih mudah dikelola.
- **Desain Sistem:** **Material 3** - Mengimplementasikan sistem desain terbaru dari Google untuk memastikan tampilan yang konsisten dan modern.
- **Manajemen State:** State management sederhana namun kuat menggunakan `remember` dan `mutableStateOf` untuk menjaga UI tetap sinkron dengan data.
- **Grafik Kustom:** Pemanfaatan **`Canvas` API** untuk menggambar komponen UI yang sepenuhnya custom (Gauge Chart) dari awal.
- **IDE:** Android Studio
- **Version Control:** Git
---
## 🚀 Instalasi & Penggunaan
1. **Prasyarat:** Pastikan Anda memiliki versi terbaru **Android Studio**.
2. **Clone Repositori:**
```sh
git clone [URL-repositori-Anda-di-sini]
```
3. **Buka di Android Studio:** Buka proyek yang telah di-clone.
4. **Sync Gradle:** Tunggu hingga Android Studio selesai mengunduh semua dependensi yang diperlukan.
5. **Jalankan Aplikasi:** Pilih target (Emulator atau perangkat fisik) dan klik tombol 'Run'.
---
## 🤖 Proses Pengembangan dengan AI
Proyek ini menjadi studi kasus menarik dalam **pengembangan perangkat lunak berbantuan AI**. Bermula dari sebuah *starter code* yang sangat dasar, sebagian besar fungsionalitas dan polesan aplikasi ini diimplementasikan melalui dialog dan iterasi dengan **Google Gemini**.
Prosesnya meliputi:
1. **Prompting Fitur:** Meminta implementasi fitur dari nol, seperti Gauge Chart.
2. **Debugging Kolaboratif:** Mengidentifikasi dan memperbaiki bug (termasuk eror pada `Canvas` dan referensi tema yang salah) dengan bantuan analisis dari AI.
3. **Refactoring & Desain:** Meminta perubahan desain, seperti skema warna dan tata letak, yang kemudian diimplementasikan oleh AI.
4. **Dokumentasi:** Menghasilkan dokumentasi ini secara bertahap sesuai progres pengembangan.
---
## 🔮 Rencana Pengembangan
Beberapa fitur potensial yang dapat ditambahkan di masa depan:
- [ ] **Penyimpanan Riwayat:** Menyimpan hasil kalkulasi BMI untuk melacak progres.
- [ ] **Grafik Tren:** Visualisasi perubahan BMI dari waktu ke waktu.
- [ ] **Dukungan Unit Imperial:** Opsi input tinggi (kaki/inci) dan berat (pon).
- [ ] **Lokalisasi:** Mendukung berbagai bahasa.
---
## 📄 Lisensi
Proyek ini dilisensikan di bawah **MIT License**. *(Disarankan untuk membuat file `LICENSE` di root proyek Anda dan menempelkan teks lisensi MIT ke dalamnya)*.
---
*Proyek ini dikembangkan dari starter code sederhana yang terinspirasi dari: Codelab "Calculate Tip" Android Developer.*

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
@ -21,7 +6,10 @@ 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.annotation.StringRes
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -30,17 +18,18 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding 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.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -48,14 +37,20 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource 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.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.example.tiptime.ui.theme.TipTimeTheme import com.example.tiptime.ui.theme.TipTimeTheme
import java.text.NumberFormat import kotlin.math.cos
import kotlin.math.sin
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -65,8 +60,9 @@ class MainActivity : ComponentActivity() {
TipTimeTheme { TipTimeTheme {
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) { ) {
TipTimeLayout() BmiCalculatorScreen()
} }
} }
} }
@ -74,62 +70,60 @@ class MainActivity : ComponentActivity() {
} }
@Composable @Composable
fun TipTimeLayout() { fun BmiCalculatorScreen() {
var amountInput by remember { mutableStateOf("") } var heightInput by remember { mutableStateOf("175") } // Default for preview
var tipInput by remember { mutableStateOf("") } var weightInput by remember { mutableStateOf("70") } // Default for preview
var roundUp by remember { mutableStateOf(false) } var ageInput by remember { mutableStateOf("25") }
val amount = amountInput.toDoubleOrNull() ?: 0.0 val heightCm = heightInput.toDoubleOrNull() ?: 0.0
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0 val weightKg = weightInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount, tipPercent, roundUp)
val bmi = calculateBMI(heightCm, weightKg)
Column( Column(
modifier = Modifier modifier = Modifier
.statusBarsPadding() .statusBarsPadding()
.padding(horizontal = 40.dp) .padding(horizontal = 24.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.safeDrawingPadding(), .safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Top
) { ) {
Spacer(modifier = Modifier.height(20.dp))
Text( Text(
text = stringResource(R.string.calculate_tip), text = stringResource(R.string.calculate_tip),
modifier = Modifier style = MaterialTheme.typography.headlineMedium,
.padding(bottom = 16.dp, top = 40.dp) color = MaterialTheme.colorScheme.primary
.align(alignment = Alignment.Start)
) )
Spacer(modifier = Modifier.height(24.dp))
EditNumberField( EditNumberField(
label = R.string.bill_amount, label = R.string.height,
leadingIcon = R.drawable.money, leadingIcon = R.drawable.number,
keyboardOptions = KeyboardOptions.Default.copy( value = heightInput,
keyboardType = KeyboardType.Number, onValueChanged = { heightInput = it },
imeAction = ImeAction.Next
),
value = amountInput,
onValueChanged = { amountInput = it },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(),
) )
Spacer(modifier = Modifier.height(16.dp))
EditNumberField( EditNumberField(
label = R.string.how_was_the_service, label = R.string.weight,
leadingIcon = R.drawable.percent, leadingIcon = R.drawable.number,
keyboardOptions = KeyboardOptions.Default.copy( value = weightInput,
keyboardType = KeyboardType.Number, onValueChanged = { weightInput = it },
imeAction = ImeAction.Done
),
value = tipInput,
onValueChanged = { tipInput = it },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(),
) )
RoundTheTipRow( Spacer(modifier = Modifier.height(16.dp))
roundUp = roundUp, EditNumberField(
onRoundUpChanged = { roundUp = it }, label = R.string.age,
modifier = Modifier.padding(bottom = 32.dp) leadingIcon = R.drawable.number,
value = ageInput,
onValueChanged = { ageInput = it },
imeAction = ImeAction.Done
) )
Text( Spacer(modifier = Modifier.height(32.dp))
text = stringResource(R.string.tip_amount, tip),
style = MaterialTheme.typography.displaySmall if (bmi > 0) {
) ResultScreen(bmi = bmi, heightCm = heightCm, weightKg = weightKg)
Spacer(modifier = Modifier.height(150.dp)) }
Spacer(modifier = Modifier.height(20.dp))
} }
} }
@ -137,60 +131,183 @@ fun TipTimeLayout() {
fun EditNumberField( fun EditNumberField(
@StringRes label: Int, @StringRes label: Int,
@DrawableRes leadingIcon: Int, @DrawableRes leadingIcon: Int,
keyboardOptions: KeyboardOptions,
value: String, value: String,
onValueChanged: (String) -> Unit, onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier imeAction: ImeAction = ImeAction.Next
) { ) {
TextField( OutlinedTextField(
value = value, value = value,
singleLine = true, singleLine = true,
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) }, modifier = Modifier.fillMaxWidth(),
modifier = modifier,
onValueChange = onValueChanged, onValueChange = onValueChanged,
label = { Text(stringResource(label)) }, label = { Text(stringResource(label)) },
keyboardOptions = keyboardOptions leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = imeAction
),
shape = RoundedCornerShape(16.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.primary,
unfocusedBorderColor = MaterialTheme.colorScheme.outline
)
) )
} }
@Composable @Composable
fun RoundTheTipRow( fun ResultScreen(bmi: Double, heightCm: Double, weightKg: Double) {
roundUp: Boolean, val category = getBmiCategory(bmi)
onRoundUpChanged: (Boolean) -> Unit, val (healthyWeightMin, healthyWeightMax) = calculateHealthyWeightRange(heightCm)
modifier: Modifier = Modifier val bmiPrime = calculateBmiPrime(bmi)
) { val ponderalIndex = calculatePonderalIndex(heightCm, weightKg)
Row(
modifier = modifier.fillMaxWidth(), Column(
verticalAlignment = Alignment.CenterVertically modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text(text = stringResource(R.string.round_up_tip)) Text(
Switch( text = stringResource(R.string.result_title),
modifier = Modifier style = MaterialTheme.typography.headlineSmall,
.fillMaxWidth() color = MaterialTheme.colorScheme.primary
.wrapContentWidth(Alignment.End), )
checked = roundUp, Spacer(modifier = Modifier.height(8.dp))
onCheckedChange = onRoundUpChanged Text(
text = stringResource(R.string.bmi_value_category, bmi, category),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(24.dp))
BmiGaugeChart(bmi = bmi)
Spacer(modifier = Modifier.height(24.dp))
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.5f))
) {
Column(modifier = Modifier.padding(16.dp)) {
ResultInfoLine(label = stringResource(R.string.healthy_bmi_range))
ResultInfoLine(
label = stringResource(
R.string.healthy_weight_for_height,
healthyWeightMin,
healthyWeightMax
)
)
ResultInfoLine(label = stringResource(R.string.bmi_prime, bmiPrime))
ResultInfoLine(label = stringResource(R.string.ponderal_index, ponderalIndex))
}
}
}
}
@Composable
fun ResultInfoLine(label: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "",
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = label,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)
}
Spacer(modifier = Modifier.height(8.dp))
}
@Composable
fun BmiGaugeChart(bmi: Double) {
val bmiMin = 16.0
val bmiMax = 40.0
val colors = listOf(
Color(0xFFD32F2F), // Underweight
Color(0xFF388E3C), // Normal
Color(0xFFFBC02D), // Overweight
Color(0xFFC62828) // Obesity
)
val onSurfaceColor = MaterialTheme.colorScheme.onSurface
Box(contentAlignment = Alignment.Center, modifier = Modifier.size(280.dp)) {
Canvas(modifier = Modifier.fillMaxSize()) {
val strokeWidth = 30.dp.toPx()
val center = Offset(size.width / 2, size.height / 2)
val radius = (size.minDimension / 2) - strokeWidth
val scale = 180f / (bmiMax - bmiMin).toFloat()
val underweightSweep = (18.5f - bmiMin.toFloat()) * scale
val normalSweep = (25f - 18.5f) * scale
val overweightSweep = (30f - 25f) * scale
val obesitySweep = (bmiMax.toFloat() - 30f) * scale
drawArc(colors[0], 180f, underweightSweep, false, style = Stroke(width = strokeWidth))
drawArc(colors[1], 180f + underweightSweep, normalSweep, false, style = Stroke(width = strokeWidth))
drawArc(colors[2], 180f + underweightSweep + normalSweep, overweightSweep, false, style = Stroke(width = strokeWidth))
drawArc(colors[3], 180f + underweightSweep + normalSweep + overweightSweep, obesitySweep, false, style = Stroke(width = strokeWidth))
val angle = 180 + (180 * ((bmi.coerceIn(bmiMin, bmiMax) - bmiMin) / (bmiMax - bmiMin))).toFloat()
val needleLength = radius - 10.dp.toPx()
val needleEndX = center.x + needleLength * cos(Math.toRadians(angle.toDouble())).toFloat()
val needleEndY = center.y + needleLength * sin(Math.toRadians(angle.toDouble())).toFloat()
drawLine(onSurfaceColor, center, Offset(needleEndX, needleEndY), 3.dp.toPx(), StrokeCap.Round)
drawCircle(onSurfaceColor, 5.dp.toPx(), center)
}
Text(
text = "BMI = %.1f".format(bmi),
style = MaterialTheme.typography.headlineLarge,
fontWeight = FontWeight.Bold,
color = onSurfaceColor
) )
} }
} }
/** private fun calculateBMI(heightCm: Double, weightKg: Double): Double {
* Calculates the tip based on the user input and format the tip amount if (heightCm <= 0 || weightKg <= 0) return 0.0
* according to the local currency. val heightInMeters = heightCm / 100.0
* Example would be "$10.00". return weightKg / (heightInMeters * heightInMeters)
*/
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) private fun getBmiCategory(bmi: Double): String {
@Composable return when {
fun TipTimeLayoutPreview() { bmi < 18.5 -> "Underweight"
TipTimeTheme { bmi < 25 -> "Normal"
TipTimeLayout() bmi < 30 -> "Overweight"
else -> "Obese"
}
}
private fun calculateHealthyWeightRange(heightCm: Double): Pair<Double, Double> {
if (heightCm <= 0) return 0.0 to 0.0
val heightInMeters = heightCm / 100.0
val minWeight = 18.5 * heightInMeters * heightInMeters
val maxWeight = 24.9 * heightInMeters * heightInMeters
return minWeight to maxWeight
}
private fun calculateBmiPrime(bmi: Double): Double {
return if (bmi > 0) bmi / 25.0 else 0.0
}
private fun calculatePonderalIndex(heightCm: Double, weightKg: Double): Double {
if (heightCm <= 0 || weightKg <= 0) return 0.0
val heightInMeters = heightCm / 100.0
return weightKg / (heightInMeters * heightInMeters * heightInMeters)
}
@Preview(showBackground = true, widthDp = 380)
@Composable
fun BmiCalculatorScreenPreview() {
TipTimeTheme {
BmiCalculatorScreen()
} }
} }

View File

@ -1,78 +1,71 @@
/*
* 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.ui.theme package com.example.tiptime.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF984061) // Custom Army Green & Brown Palette
val md_theme_light_onPrimary = Color(0xFFFFFFFF) val armyGreen = Color(0xFF4B5320)
val md_theme_light_primaryContainer = Color(0xFFFFD9E2) val darkBrown = Color(0xFF5C4033)
val md_theme_light_onPrimaryContainer = Color(0xFF3E001D) val lightCream = Color(0xFFF5F5DC)
val md_theme_light_secondary = Color(0xFF754B9C) val tan = Color(0xFFD2B48C)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFF1DBFF) // Material 3 Light Theme Colors
val md_theme_light_onSecondaryContainer = Color(0xFF2D0050) val md_theme_light_primary = armyGreen
val md_theme_light_tertiary = Color(0xFF984060) val md_theme_light_onPrimary = lightCream
val md_theme_light_onTertiary = Color(0xFFFFFFFF) val md_theme_light_primaryContainer = tan
val md_theme_light_tertiaryContainer = Color(0xFFFFD9E2) val md_theme_light_onPrimaryContainer = darkBrown
val md_theme_light_onTertiaryContainer = Color(0xFF3E001D) val md_theme_light_secondary = darkBrown
val md_theme_light_onSecondary = lightCream
val md_theme_light_secondaryContainer = tan
val md_theme_light_onSecondaryContainer = darkBrown
val md_theme_light_tertiary = tan
val md_theme_light_onTertiary = darkBrown
val md_theme_light_tertiaryContainer = armyGreen
val md_theme_light_onTertiaryContainer = lightCream
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)
val md_theme_light_onErrorContainer = Color(0xFF410002) val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFAFCFF) val md_theme_light_background = lightCream
val md_theme_light_onBackground = Color(0xFF001F2A) val md_theme_light_onBackground = darkBrown
val md_theme_light_surface = Color(0xFFFAFCFF) val md_theme_light_surface = lightCream
val md_theme_light_onSurface = Color(0xFF001F2A) val md_theme_light_onSurface = darkBrown
val md_theme_light_surfaceVariant = Color(0xFFF2DDE2) val md_theme_light_surfaceVariant = tan
val md_theme_light_onSurfaceVariant = Color(0xFF514347) val md_theme_light_onSurfaceVariant = darkBrown
val md_theme_light_outline = Color(0xFF837377) val md_theme_light_outline = armyGreen
val md_theme_light_inverseOnSurface = Color(0xFFE1F4FF) val md_theme_light_inverseOnSurface = lightCream
val md_theme_light_inverseSurface = Color(0xFF003547) val md_theme_light_inverseSurface = darkBrown
val md_theme_light_inversePrimary = Color(0xFFFFB0C8) val md_theme_light_inversePrimary = lightCream
val md_theme_light_surfaceTint = Color(0xFF984061) val md_theme_light_surfaceTint = armyGreen
val md_theme_light_outlineVariant = Color(0xFFD5C2C6) val md_theme_light_outlineVariant = tan
val md_theme_light_scrim = Color(0xFF000000) val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFFFFB0C8) // Material 3 Dark Theme Colors
val md_theme_dark_onPrimary = Color(0xFF5E1133) val md_theme_dark_primary = tan
val md_theme_dark_primaryContainer = Color(0xFF7B2949) val md_theme_dark_onPrimary = darkBrown
val md_theme_dark_onPrimaryContainer = Color(0xFFFFD9E2) val md_theme_dark_primaryContainer = armyGreen
val md_theme_dark_secondary = Color(0xFFDEB7FF) val md_theme_dark_onPrimaryContainer = lightCream
val md_theme_dark_onSecondary = Color(0xFF44196A) val md_theme_dark_secondary = tan
val md_theme_dark_secondaryContainer = Color(0xFF5C3382) val md_theme_dark_onSecondary = darkBrown
val md_theme_dark_onSecondaryContainer = Color(0xFFF1DBFF) val md_theme_dark_secondaryContainer = armyGreen
val md_theme_dark_tertiary = Color(0xFFFFB1C7) val md_theme_dark_onSecondaryContainer = lightCream
val md_theme_dark_onTertiary = Color(0xFF5E1132) val md_theme_dark_tertiary = lightCream
val md_theme_dark_tertiaryContainer = Color(0xFF7B2948) val md_theme_dark_onTertiary = armyGreen
val md_theme_dark_onTertiaryContainer = Color(0xFFFFD9E2) val md_theme_dark_tertiaryContainer = darkBrown
val md_theme_dark_onTertiaryContainer = lightCream
val md_theme_dark_error = Color(0xFFFFB4AB) val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A) val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005) val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF001F2A) val md_theme_dark_background = darkBrown
val md_theme_dark_onBackground = Color(0xFFBFE9FF) val md_theme_dark_onBackground = lightCream
val md_theme_dark_surface = Color(0xFF001F2A) val md_theme_dark_surface = darkBrown
val md_theme_dark_onSurface = Color(0xFFBFE9FF) val md_theme_dark_onSurface = lightCream
val md_theme_dark_surfaceVariant = Color(0xFF514347) val md_theme_dark_surfaceVariant = armyGreen
val md_theme_dark_onSurfaceVariant = Color(0xFFD5C2C6) val md_theme_dark_onSurfaceVariant = lightCream
val md_theme_dark_outline = Color(0xFF9E8C90) val md_theme_dark_outline = tan
val md_theme_dark_inverseOnSurface = Color(0xFF001F2A) val md_theme_dark_inverseOnSurface = darkBrown
val md_theme_dark_inverseSurface = Color(0xFFBFE9FF) val md_theme_dark_inverseSurface = lightCream
val md_theme_dark_inversePrimary = Color(0xFF984061) val md_theme_dark_inversePrimary = armyGreen
val md_theme_dark_surfaceTint = Color(0xFFFFB0C8) val md_theme_dark_surfaceTint = tan
val md_theme_dark_outlineVariant = Color(0xFF514347) val md_theme_dark_outlineVariant = armyGreen
val md_theme_dark_scrim = Color(0xFF000000) val md_theme_dark_scrim = Color(0xFF000000)

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

@ -1,24 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<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">BMI Calculator</string>
<string name="bill_amount">Bill Amount</string> <string name="height">Tinggi Badan (cm)</string>
<string name="how_was_the_service">Tip Percentage</string> <string name="weight">Berat Badan (kg)</string>
<string name="round_up_tip">Round up tip?</string> <string name="age">Umur</string>
<string name="tip_amount">Tip Amount: %s</string>
<!-- Result Screen -->
<string name="result_title">Result</string>
<string name="bmi_value_category">BMI = %.1f kg/m² (%s)</string>
<string name="healthy_bmi_range">Healthy BMI range: 18.5 kg/m² - 25 kg/m²</string>
<string name="healthy_weight_for_height">Healthy weight for your height: %.1f kg - %.1f kg</string>
<string name="bmi_prime">BMI Prime: %.2f</string>
<string name="ponderal_index">Ponderal Index: %.1f kg/m³</string>
<!-- BMI Categories -->
<string name="underweight">Underweight</string>
<string name="normal">Normal</string>
<string name="overweight">Overweight</string>
<string name="obesity">Obesity</string>
</resources> </resources>

View File

@ -16,8 +16,8 @@
// 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.8.0" apply false id("com.android.application") version "8.13.0" apply false
id("com.android.library") version "8.8.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.android") version "2.1.0" apply false
id("org.jetbrains.kotlin.plugin.compose") 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.12-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