From f17e5803978bdd3c578de2af6fc89882db35883c Mon Sep 17 00:00:00 2001 From: 202310715096-JEREMIA-SEBASTIAN-MARPAUNG <202310715096@mhs.ubharajaya.ac.id> Date: Fri, 7 Nov 2025 13:24:45 +0700 Subject: [PATCH] push --- README.md | 73 ++- app/build.gradle.kts | 54 +-- app/src/main/AndroidManifest.xml | 24 +- .../java/com/example/BMIcalculatorScreen.kt | 3 + .../MainActivity.kt | 422 ++++++++++++++++++ .../java/com/example/tiptime/MainActivity.kt | 196 -------- .../com/example/tiptime/ui/theme/Theme.kt | 115 ----- app/src/main/res/values/strings.xml | 4 +- 8 files changed, 508 insertions(+), 383 deletions(-) create mode 100644 app/src/main/java/com/example/BMIcalculatorScreen.kt create mode 100644 app/src/main/java/com/example/basic_android_kotlin_compose_training_tip_calculator/MainActivity.kt delete mode 100644 app/src/main/java/com/example/tiptime/MainActivity.kt delete mode 100644 app/src/main/java/com/example/tiptime/ui/theme/Theme.kt diff --git a/README.md b/README.md index 4ab87ef..2298bd6 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,59 @@ -Tip Time - Solution Code -================================= +# Aplikasi Kalkulator BMI -Solution code for the [Android Basics with Compose](https://developer.android.com/courses/android-basics-compose/course): Tip Time app. +**Dibuat oleh:** +**Nama:** Jeremia Sebastian Marpaung +**NPM:** 202310715096 +**Kelas:** Pemrograman Perangkat Bergerak - F5A5 + +--- + +## Deskripsi Aplikasi +Aplikasi **Kalkulator BMI (Body Mass Index)** ini dibuat sebagai proyek akhir mata kuliah **Pemrograman Perangkat Bergerak**. +Tujuannya adalah membantu pengguna menghitung **Indeks Massa Tubuh (BMI)** berdasarkan **berat badan (kg/lbs)** dan **tinggi badan (cm/inci)** agar dapat mengetahui apakah berat badan tergolong **Kurus(Underweight),Normal,Overweight,Obesitas**. + +Aplikasi memiliki **satu halaman utama**: + +1.**Halaman Utama (Kalkulator BMI)** + Pengguna dapat menginput berat dan tinggi badan, menekan tombol **“Hitung BMI”**, dan melihat hasil nilai BMI beserta **kategori serta saran kesehatannya**. + +--- + +## Fitur Utama +- Input berat dan tinggi badan secara interaktif (bisa satuan **SI** atau **USC**). +- Perhitungan otomatis nilai BMI dengan opsi pembulatan hasil. +- Tampilan kategori hasil (Kurus(Underweight), Normal, Overweight, Obesitas). +- Antarmuka sederhana, bersih, dan responsif menggunakan **Jetpack Compose**. +- Navigasi antarhalaman dengan tombol **MULAI** dari halaman biodata. + +--- + +## Teknologi yang Digunakan +- **Android Studio (Kotlin)** +- **Jetpack Compose** & **XML Layouts** untuk desain antarmuka +- **Intent** untuk navigasi antar activity +- **Drawable XML & colors.xml** untuk tema warna dan efek gradasi +- **Unit Test (disarankan)** untuk menguji akurasi perhitungan BMI + +--- + +## Kontribusi & Kredit +Aplikasi ini dikembangkan dengan bantuan **Claude.ai** dalam pembuatan kode, desain antarmuka, dan dokumentasi. +Semua logika perhitungan, pengujian, dan penyempurnaan dilakukan mandiri oleh pengembang. + +--- + +## Change Log (Ringkas) +- **Migrasi kode dasar** dari kalkulator tip ke kalkulator BMI berbasis Kotlin Compose. +- **Penambahan mode satuan USC (Inci & Lbs)** dengan validasi tinggi minimal 4 inci. +- **Perbaikan rumus perhitungan BMI** agar sesuai standar WHO. +- **Desain ulang Splash Screen** dengan tombol “MULAI” berwarna hijau dan latar gradasi biru-hijau. +- **Optimalisasi UX** — hasil BMI hanya muncul setelah tombol **“Hitung BMI”** ditekan. -Introduction ------------- -The Tip Time app contains various UI elements for calculating a tip, -teaching about user input, and State in Compose. +--- +## Lisensi +Proyek ini dibuat untuk tujuan pembelajaran dalam mata kuliah **Pemrograman Perangkat Bergerak** dan tidak untuk tujuan komersial. +Lisensi mengikuti [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). -Pre-requisites --------------- -* Experience with Kotlin syntax. -* How to create and run a project in Android Studio. - - -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. +--- \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f786714..6ee5e03 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,19 +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. - */ - plugins { id("com.android.application") id("org.jetbrains.kotlin.android") @@ -21,6 +5,7 @@ plugins { } android { + namespace = "com.example.tiptime" compileSdk = 35 defaultConfig { @@ -45,41 +30,46 @@ android { ) } } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.toString() + jvmTarget = "17" } + buildFeatures { compose = true } + packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } - namespace = "com.example.tiptime" } dependencies { + // Compose BOM + implementation(platform("androidx.compose:compose-bom:2024.02.00")) - implementation(platform("androidx.compose:compose-bom:2024.12.01")) - implementation("androidx.activity:activity-compose:1.9.3") - implementation("androidx.compose.material3:material3") + // Jetpack Compose implementation("androidx.compose.ui:ui") - implementation("androidx.compose.ui:ui-tooling") + implementation("androidx.compose.material3:material3") implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.core:core-ktx:1.15.0") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7") + debugImplementation("androidx.compose.ui:ui-tooling") + implementation("androidx.compose.material:material-icons-extended") + // AndroidX + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + implementation("androidx.activity:activity-compose:1.8.2") + implementation("androidx.navigation:navigation-compose:2.7.7") + + // Testing testImplementation("junit:junit:4.13.2") - - androidTestImplementation(platform("androidx.compose:compose-bom:2024.12.01")) - androidTestImplementation("androidx.compose.ui:ui-test-junit4") - androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") - androidTestImplementation("androidx.test.ext:junit:1.2.1") - - debugImplementation("androidx.compose.ui:ui-test-manifest") -} + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e897dec..0a60381 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,20 +1,4 @@ - - @@ -24,12 +8,12 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.TipTime" + android:theme="@android:style/Theme.Material.Light.NoActionBar" tools:targetApi="33"> + android:theme="@android:style/Theme.Material.Light.NoActionBar"> @@ -37,4 +21,4 @@ - + \ No newline at end of file diff --git a/app/src/main/java/com/example/BMIcalculatorScreen.kt b/app/src/main/java/com/example/BMIcalculatorScreen.kt new file mode 100644 index 0000000..076b6a9 --- /dev/null +++ b/app/src/main/java/com/example/BMIcalculatorScreen.kt @@ -0,0 +1,3 @@ +package com.example + +annotation class BMIcalculatorScreen diff --git a/app/src/main/java/com/example/basic_android_kotlin_compose_training_tip_calculator/MainActivity.kt b/app/src/main/java/com/example/basic_android_kotlin_compose_training_tip_calculator/MainActivity.kt new file mode 100644 index 0000000..5c6482c --- /dev/null +++ b/app/src/main/java/com/example/basic_android_kotlin_compose_training_tip_calculator/MainActivity.kt @@ -0,0 +1,422 @@ +package com.example.basic_android_kotlin_compose_training_tip_calculator + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import kotlin.math.round + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + BMICalculatorApp() + } + } +} + +@Composable +fun BMICalculatorApp() { + val navController = rememberNavController() + + NavHost( + navController = navController, + startDestination = "home" + ) { + composable("home") { HomePage(navController) } + composable("calculator") { CalculatorPage(navController) } + composable("info") { InfoPage(navController) } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HomePage(navController: NavHostController) { + Scaffold( + topBar = { + TopAppBar( + title = { Text("BMI Calculator") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color(0xFF00BCD4), + titleContentColor = Color.White + ) + ) + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + "Selamat Datang!", + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + color = Color(0xFF6200EE) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + "Hitung BMI Anda dengan mudah", + fontSize = 18.sp, + color = Color.Gray + ) + + Spacer(modifier = Modifier.height(48.dp)) + + Button( + onClick = { navController.navigate("calculator") }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF6200EE) + ) + ) { + Text("Mulai Hitung BMI", fontSize = 18.sp) + } + + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedButton( + onClick = { navController.navigate("info") }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(12.dp) + ) { + Icon(Icons.Default.Info, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text("Informasi BMI", fontSize = 18.sp) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CalculatorPage(navController: NavHostController) { + var unitSystem by remember { mutableStateOf("Metric") } + var weight by remember { mutableStateOf("") } + var height by remember { mutableStateOf("") } + var feet by remember { mutableStateOf("") } + var inches by remember { mutableStateOf("") } + var bmi by remember { mutableDoubleStateOf(0.0) } + var category by remember { mutableStateOf("") } + var color by remember { mutableStateOf(Color.Gray) } + + Scaffold( + topBar = { + TopAppBar( + title = { Text("Kalkulator BMI") }, + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon(Icons.Default.ArrowBack, contentDescription = "Back", tint = Color.White) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color(0xFF6200EE), + titleContentColor = Color.White + ) + ) + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Unit System Selector + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + FilterChip( + selected = unitSystem == "Metric", + onClick = { + unitSystem = "Metric" + weight = "" + height = "" + bmi = 0.0 + }, + label = { Text("Metric (kg/cm)") }, + modifier = Modifier.weight(1f).padding(end = 8.dp) + ) + + FilterChip( + selected = unitSystem == "USC", + onClick = { + unitSystem = "USC" + weight = "" + feet = "" + inches = "" + bmi = 0.0 + }, + label = { Text("USC (lbs/ft/in)") }, + modifier = Modifier.weight(1f).padding(start = 8.dp) + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + if (unitSystem == "Metric") { + // Metric System + OutlinedTextField( + value = weight, + onValueChange = { weight = it }, + label = { Text("Berat Badan (kg)") }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(12.dp)) + + OutlinedTextField( + value = height, + onValueChange = { height = it }, + label = { Text("Tinggi Badan (cm)") }, + modifier = Modifier.fillMaxWidth() + ) + } else { + // USC System + OutlinedTextField( + value = weight, + onValueChange = { weight = it }, + label = { Text("Berat Badan (lbs)") }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + OutlinedTextField( + value = feet, + onValueChange = { feet = it }, + label = { Text("Tinggi (feet)") }, + modifier = Modifier.weight(1f) + ) + + OutlinedTextField( + value = inches, + onValueChange = { inches = it }, + label = { Text("Tinggi (inch)") }, + modifier = Modifier.weight(1f) + ) + } + } + + Spacer(modifier = Modifier.height(20.dp)) + + Button( + onClick = { + if (unitSystem == "Metric") { + val w = weight.toDoubleOrNull() ?: 0.0 + val h = (height.toDoubleOrNull() ?: 0.0) / 100.0 + bmi = if (h > 0) round(w / (h * h) * 100) / 100.0 else 0.0 + } else { + val w = weight.toDoubleOrNull() ?: 0.0 + val f = feet.toDoubleOrNull() ?: 0.0 + val i = inches.toDoubleOrNull() ?: 0.0 + val totalInches = (f * 12) + i + // BMI = (weight in lbs / (height in inches)^2) * 703 + bmi = if (totalInches > 0) round((w / (totalInches * totalInches)) * 703 * 100) / 100.0 else 0.0 + } + + when { + bmi < 18.5 -> { + category = "Kurus (Underweight)" + color = Color(0xFF42A5F5) + } + bmi < 25.0 -> { + category = "Normal" + color = Color(0xFF66BB6A) + } + bmi < 30.0 -> { + category = "Overweight" + color = Color(0xFFFFA726) + } + else -> { + category = "Obesitas" + color = Color(0xFFEF5350) + } + } + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF6200EE) + ) + ) { + Text("Hitung BMI", fontSize = 18.sp) + } + + Spacer(modifier = Modifier.height(24.dp)) + + if (bmi > 0.0) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors( + containerColor = color.copy(alpha = 0.2f) + ) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "BMI: $bmi", + fontSize = 28.sp, + fontWeight = FontWeight.Bold, + color = color + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = category, + fontSize = 22.sp, + fontWeight = FontWeight.Medium, + color = color + ) + } + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun InfoPage(navController: NavHostController) { + Scaffold( + topBar = { + TopAppBar( + title = { Text("Informasi BMI") }, + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon(Icons.Default.ArrowBack, contentDescription = "Back", tint = Color.White) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color(0xFF6200EE), + titleContentColor = Color.White + ) + ) + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(24.dp) + ) { + Text( + "Kategori BMI", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = Color(0xFF6200EE) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + BMIInfoCard("Kurus (Underweight)", "BMI < 18.5", Color(0xFF42A5F5)) + Spacer(modifier = Modifier.height(12.dp)) + + BMIInfoCard("Normal", "BMI 18.5 - 24.9", Color(0xFF66BB6A)) + Spacer(modifier = Modifier.height(12.dp)) + + BMIInfoCard("Overweight", "BMI 25.0 - 29.9", Color(0xFFFFA726)) + Spacer(modifier = Modifier.height(12.dp)) + + BMIInfoCard("Obesitas", "BMI ≥ 30.0", Color(0xFFEF5350)) + + Spacer(modifier = Modifier.height(24.dp)) + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = Color(0xFFF5F5F5) + ), + shape = RoundedCornerShape(12.dp) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + "Tentang BMI", + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + "Body Mass Index (BMI) adalah ukuran lemak tubuh berdasarkan tinggi dan berat badan. BMI adalah indikator yang baik untuk mengetahui apakah berat badan Anda ideal atau tidak.", + fontSize = 14.sp, + lineHeight = 20.sp + ) + } + } + } + } +} + +@Composable +fun BMIInfoCard(title: String, range: String, color: Color) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = color.copy(alpha = 0.2f) + ), + shape = RoundedCornerShape(12.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column { + Text( + text = title, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = color + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = range, + fontSize = 14.sp, + color = color.copy(alpha = 0.8f) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tiptime/MainActivity.kt b/app/src/main/java/com/example/tiptime/MainActivity.kt deleted file mode 100644 index d8ccd56..0000000 --- a/app/src/main/java/com/example/tiptime/MainActivity.kt +++ /dev/null @@ -1,196 +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. - */ -package com.example.tiptime - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawingPadding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.example.tiptime.ui.theme.TipTimeTheme -import java.text.NumberFormat - -class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - enableEdgeToEdge() - super.onCreate(savedInstanceState) - setContent { - TipTimeTheme { - Surface( - modifier = Modifier.fillMaxSize(), - ) { - TipTimeLayout() - } - } - } - } -} - -@Composable -fun TipTimeLayout() { - var amountInput by remember { mutableStateOf("") } - var tipInput by remember { mutableStateOf("") } - var roundUp by remember { mutableStateOf(false) } - - val amount = amountInput.toDoubleOrNull() ?: 0.0 - val tipPercent = tipInput.toDoubleOrNull() ?: 0.0 - val tip = calculateTip(amount, tipPercent, roundUp) - - Column( - modifier = Modifier - .statusBarsPadding() - .padding(horizontal = 40.dp) - .verticalScroll(rememberScrollState()) - .safeDrawingPadding(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - text = stringResource(R.string.calculate_tip), - modifier = Modifier - .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 EditNumberField( - @StringRes label: Int, - @DrawableRes leadingIcon: Int, - keyboardOptions: KeyboardOptions, - value: String, - onValueChanged: (String) -> Unit, - modifier: Modifier = Modifier -) { - TextField( - value = value, - singleLine = true, - leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) }, - modifier = modifier, - onValueChange = onValueChanged, - label = { Text(stringResource(label)) }, - keyboardOptions = keyboardOptions - ) -} - -@Composable -fun RoundTheTipRow( - roundUp: Boolean, - onRoundUpChanged: (Boolean) -> Unit, - modifier: Modifier = Modifier -) { - Row( - modifier = modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Text(text = stringResource(R.string.round_up_tip)) - Switch( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.End), - checked = roundUp, - onCheckedChange = onRoundUpChanged - ) - } -} - -/** - * 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 TipTimeLayoutPreview() { - TipTimeTheme { - TipTimeLayout() - } -} diff --git a/app/src/main/java/com/example/tiptime/ui/theme/Theme.kt b/app/src/main/java/com/example/tiptime/ui/theme/Theme.kt deleted file mode 100644 index b8c9adb..0000000 --- a/app/src/main/java/com/example/tiptime/ui/theme/Theme.kt +++ /dev/null @@ -1,115 +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. - */ -package com.example.tiptime.ui.theme - -import android.os.Build -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalContext - -private val LightColorScheme = lightColorScheme( - primary = md_theme_light_primary, - onPrimary = md_theme_light_onPrimary, - primaryContainer = md_theme_light_primaryContainer, - onPrimaryContainer = md_theme_light_onPrimaryContainer, - secondary = md_theme_light_secondary, - onSecondary = md_theme_light_onSecondary, - secondaryContainer = md_theme_light_secondaryContainer, - onSecondaryContainer = md_theme_light_onSecondaryContainer, - tertiary = md_theme_light_tertiary, - onTertiary = md_theme_light_onTertiary, - tertiaryContainer = md_theme_light_tertiaryContainer, - onTertiaryContainer = md_theme_light_onTertiaryContainer, - error = md_theme_light_error, - errorContainer = md_theme_light_errorContainer, - onError = md_theme_light_onError, - onErrorContainer = md_theme_light_onErrorContainer, - background = md_theme_light_background, - onBackground = md_theme_light_onBackground, - surface = md_theme_light_surface, - onSurface = md_theme_light_onSurface, - surfaceVariant = md_theme_light_surfaceVariant, - onSurfaceVariant = md_theme_light_onSurfaceVariant, - outline = md_theme_light_outline, - inverseOnSurface = md_theme_light_inverseOnSurface, - inverseSurface = md_theme_light_inverseSurface, - inversePrimary = md_theme_light_inversePrimary, - surfaceTint = md_theme_light_surfaceTint, - outlineVariant = md_theme_light_outlineVariant, - scrim = md_theme_light_scrim, -) - -private val DarkColorScheme = darkColorScheme( - primary = md_theme_dark_primary, - onPrimary = md_theme_dark_onPrimary, - primaryContainer = md_theme_dark_primaryContainer, - onPrimaryContainer = md_theme_dark_onPrimaryContainer, - secondary = md_theme_dark_secondary, - onSecondary = md_theme_dark_onSecondary, - secondaryContainer = md_theme_dark_secondaryContainer, - onSecondaryContainer = md_theme_dark_onSecondaryContainer, - tertiary = md_theme_dark_tertiary, - onTertiary = md_theme_dark_onTertiary, - tertiaryContainer = md_theme_dark_tertiaryContainer, - onTertiaryContainer = md_theme_dark_onTertiaryContainer, - error = md_theme_dark_error, - errorContainer = md_theme_dark_errorContainer, - onError = md_theme_dark_onError, - onErrorContainer = md_theme_dark_onErrorContainer, - background = md_theme_dark_background, - onBackground = md_theme_dark_onBackground, - surface = md_theme_dark_surface, - onSurface = md_theme_dark_onSurface, - surfaceVariant = md_theme_dark_surfaceVariant, - onSurfaceVariant = md_theme_dark_onSurfaceVariant, - outline = md_theme_dark_outline, - inverseOnSurface = md_theme_dark_inverseOnSurface, - inverseSurface = md_theme_dark_inverseSurface, - inversePrimary = md_theme_dark_inversePrimary, - surfaceTint = md_theme_dark_surfaceTint, - outlineVariant = md_theme_dark_outlineVariant, - scrim = md_theme_dark_scrim, -) - -@Composable -fun TipTimeTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ - // Dynamic color in this app is turned off for learning purposes - dynamicColor: Boolean = false, - content: @Composable () -> Unit -) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } - - darkTheme -> DarkColorScheme - else -> LightColorScheme - } - - MaterialTheme( - colorScheme = colorScheme, - typography = Typography, - content = content - ) -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 26d4260..1504f5d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,5 +20,7 @@ Bill Amount Tip Percentage Round up tip? - Tip Amount: %s + Tip Amount: %1$s + +