Compare commits
No commits in common. "fa6c42e1669644b9c6c5a0e2b4e3267140a8236e" and "7db6c366a35ad2e586be12c09f0610c86f0bed6b" have entirely different histories.
fa6c42e166
...
7db6c366a3
46
README.md
46
README.md
@ -1,36 +1,24 @@
|
|||||||
# Proyek UTS - Aplikasi Kalkulator BMI
|
Tip Time - Solution Code
|
||||||
|
=================================
|
||||||
|
|
||||||
Sebuah aplikasi Android modern yang saya buat untuk tugas Ujian Tengah Semester (UTS). Aplikasi ini berfungsi sebagai kalkulator Indeks Massa Tubuh (BMI) yang fungsional dan punya tampilan menarik.
|
Solution code for the [Android Basics with Compose](https://developer.android.com/courses/android-basics-compose/course): Tip Time app.
|
||||||
|
|
||||||
- **Nama:** Hadi Prakoso
|
|
||||||
- **NPM:** 202310715312
|
|
||||||
|
|
||||||
## Fitur Utama
|
Introduction
|
||||||
|
------------
|
||||||
|
The Tip Time app contains various UI elements for calculating a tip,
|
||||||
|
teaching about user input, and State in Compose.
|
||||||
|
|
||||||
- **Kalkulasi BMI Akurat**: Menghitung BMI berdasarkan berat dan tinggi badan pengguna.
|
|
||||||
- **Dua Sistem Unit**: Mendukung sistem Metrik (kg, cm) dan USC (lbs, inches).
|
|
||||||
- **Input Lengkap**: Memasukkan data tambahan seperti Umur dan Jenis Kelamin untuk konteks.
|
|
||||||
- **Visualisasi Hasil**: Menampilkan hasil dengan speedometer interaktif dan kategori yang jelas (Underweight, Normal, Overweight, Obesity).
|
|
||||||
- **Detail Tambahan**: Memberikan informasi bermanfaat seperti:
|
|
||||||
- Rentang BMI Sehat (18.5 - 25.0)
|
|
||||||
- Rentang Berat Badan Sehat (dihitung berdasarkan tinggi)
|
|
||||||
- BMI Prime
|
|
||||||
- Indeks Ponderal
|
|
||||||
- **Validasi Input**: Mencegah pengguna memasukkan data yang tidak valid atau tidak wajar (misalnya, tinggi atau berat badan nol/negatif, umur tidak wajar).
|
|
||||||
- **UI/UX Modern**: Dibuat dengan Jetpack Compose, menampilkan tema gelap yang elegan dan mudah digunakan.
|
|
||||||
|
|
||||||
## Proses Pengembangan & Peran AI (Gemini)
|
Pre-requisites
|
||||||
|
--------------
|
||||||
|
* Experience with Kotlin syntax.
|
||||||
|
* How to create and run a project in Android Studio.
|
||||||
|
|
||||||
Dalam pengerjaan proyek ini, saya banyak dibantu oleh **AI Gemini** sebagai asisten coding. Prosesnya jadi lebih cepat dan saya bisa belajar banyak hal baru.
|
|
||||||
|
|
||||||
- **Debugging**: Saat jarum speedometer tidak bergerak atau ada error `type mismatch` di unit test, Gemini membantu menemukan penyebab dan memberikan solusinya.
|
Getting Started
|
||||||
- **Refactoring & Best Practice**: Gemini memberikan masukan agar kode saya lebih rapi dan sesuai dengan praktik terbaik, misalnya memisahkan logika dari UI.
|
---------------
|
||||||
- **Dokumentasi & Unit Test**: Saya juga dibantu dalam membuat kerangka awal untuk file `README.md` ini dan file `BMICalculatorTest.kt`, yang kemudian saya sesuaikan lagi.
|
1. Install Android Studio, if you don't already have it.
|
||||||
|
2. Download the sample.
|
||||||
Bantuan dari Gemini sangat mempercepat proses development, terutama saat menghadapi *error* atau butuh ide untuk struktur kode yang lebih baik.
|
3. Import the sample into Android Studio.
|
||||||
|
4. Build and run the sample.
|
||||||
## Aspek Teknis Lainnya
|
|
||||||
|
|
||||||
- **Unit Testing**: Proyek ini dilengkapi dengan unit test (`BMICalculatorTest.kt`) untuk memverifikasi akurasi fungsi `calculateBmi` dan `determineBmiCategory`.
|
|
||||||
- **Kualitas Kode**: Kode diusahakan agar mudah dibaca dan modular. Adanya komentar di bagian-bagian penting juga membantu menjelaskan alur program.
|
|
||||||
- **Penggunaan Git**: Setiap perubahan signifikan pada proyek ini saya lacak menggunakan Git, lengkap dengan pesan *commit* yang deskriptif.
|
|
||||||
|
|||||||
@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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 {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
@ -5,13 +21,12 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.tiptime"
|
compileSdk = 35
|
||||||
compileSdk = 34
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.example.tiptime"
|
applicationId = "com.example.tiptime"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
@ -31,11 +46,11 @@ 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
|
||||||
@ -45,23 +60,26 @@ android {
|
|||||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
namespace = "com.example.tiptime"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.core:core-ktx:1.12.0")
|
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
implementation(platform("androidx.compose:compose-bom:2024.11.00"))
|
||||||
implementation("androidx.activity:activity-compose:1.8.2")
|
implementation("androidx.activity:activity-compose:1.9.3")
|
||||||
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
|
|
||||||
implementation("androidx.compose.ui:ui")
|
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.ui:ui")
|
||||||
|
implementation("androidx.compose.ui:ui-tooling")
|
||||||
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
|
implementation("androidx.core:core-ktx:1.15.0")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
|
||||||
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation(platform("androidx.compose:compose-bom:2024.11.00"))
|
||||||
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
|
|
||||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.2.1")
|
||||||
|
|
||||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,373 +1,196 @@
|
|||||||
// NAMA: HADI PRAKOSO
|
/*
|
||||||
// NPM: 202310715312
|
* 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
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.*
|
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.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.material.icons.Icons
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material.icons.filled.DateRange
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material.icons.filled.LineWeight
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material.icons.filled.SwapVert
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.runtime.*
|
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.geometry.Offset
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.graphics.StrokeCap
|
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
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 androidx.compose.ui.unit.sp
|
|
||||||
import com.example.tiptime.ui.theme.TipTimeTheme
|
import com.example.tiptime.ui.theme.TipTimeTheme
|
||||||
import java.util.Locale
|
import java.text.NumberFormat
|
||||||
import kotlin.math.cos
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.math.sin
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
TipTimeTheme(darkTheme = true) {
|
TipTimeTheme {
|
||||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
Surface(
|
||||||
BmiCalculatorScreen()
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
TipTimeLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class BmiFullResult(
|
|
||||||
val bmi: Double,
|
|
||||||
val category: String,
|
|
||||||
val healthyBmiRange: String = "18.5 - 25.0",
|
|
||||||
val healthyWeightRange: String,
|
|
||||||
val bmiPrime: Double,
|
|
||||||
val ponderalIndex: Double
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BmiCalculatorScreen() {
|
fun TipTimeLayout() {
|
||||||
var heightInput by remember { mutableStateOf("") }
|
var amountInput by remember { mutableStateOf("") }
|
||||||
var weightInput by remember { mutableStateOf("") }
|
var tipInput by remember { mutableStateOf("") }
|
||||||
var ageInput by remember { mutableStateOf("") }
|
var roundUp by remember { mutableStateOf(false) }
|
||||||
var selectedGender by remember { mutableStateOf("Laki-laki") }
|
|
||||||
var useMetricUnits by remember { mutableStateOf(true) }
|
|
||||||
var bmiResult by remember { mutableStateOf<BmiFullResult?>(null) }
|
|
||||||
var validationError by remember { mutableStateOf<String?>(null) }
|
|
||||||
|
|
||||||
fun resetFields() {
|
val amount = amountInput.toDoubleOrNull() ?: 0.0
|
||||||
heightInput = ""
|
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
|
||||||
weightInput = ""
|
val tip = calculateTip(amount, tipPercent, roundUp)
|
||||||
ageInput = ""
|
|
||||||
selectedGender = "Laki-laki"
|
|
||||||
useMetricUnits = true
|
|
||||||
bmiResult = null
|
|
||||||
validationError = null
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.statusBarsPadding()
|
||||||
.padding(16.dp)
|
.padding(horizontal = 40.dp)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState())
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
.safeDrawingPadding(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text("Kalkulator BMI", style = MaterialTheme.typography.headlineLarge, fontWeight = FontWeight.Bold)
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
Text("by Hadi Prakoso", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.primary)
|
|
||||||
Spacer(Modifier.height(24.dp))
|
|
||||||
|
|
||||||
AnimatedVisibility(visible = bmiResult != null) {
|
|
||||||
bmiResult?.let { ResultDisplay(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bmiResult != null) {
|
|
||||||
Spacer(Modifier.height(24.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
InputPanel(
|
|
||||||
heightInput = heightInput, onHeightChange = { heightInput = it; validationError = null },
|
|
||||||
weightInput = weightInput, onWeightChange = { weightInput = it; validationError = null },
|
|
||||||
ageInput = ageInput, onAgeChange = { ageInput = it; validationError = null },
|
|
||||||
selectedGender = selectedGender, onGenderSelect = { selectedGender = it },
|
|
||||||
useMetric = useMetricUnits, onUnitChange = { useMetricUnits = it }
|
|
||||||
)
|
|
||||||
|
|
||||||
validationError?.let {
|
|
||||||
Text(
|
Text(
|
||||||
text = it,
|
text = stringResource(R.string.calculate_tip),
|
||||||
color = MaterialTheme.colorScheme.error,
|
modifier = Modifier
|
||||||
style = MaterialTheme.typography.bodySmall,
|
.padding(bottom = 16.dp, top = 40.dp)
|
||||||
modifier = Modifier.padding(top = 8.dp)
|
.align(alignment = Alignment.Start)
|
||||||
)
|
)
|
||||||
}
|
EditNumberField(
|
||||||
|
label = R.string.bill_amount,
|
||||||
Spacer(Modifier.height(24.dp))
|
leadingIcon = R.drawable.money,
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
ActionButtons(
|
keyboardType = KeyboardType.Number,
|
||||||
onCalculate = {
|
imeAction = ImeAction.Next
|
||||||
val height = heightInput.toDoubleOrNull()
|
|
||||||
val weight = weightInput.toDoubleOrNull()
|
|
||||||
val age = ageInput.toIntOrNull()
|
|
||||||
|
|
||||||
// Input Validation
|
|
||||||
when {
|
|
||||||
height == null || weight == null || age == null -> {
|
|
||||||
validationError = "Semua kolom (Umur, Tinggi, Berat) harus diisi dengan angka."
|
|
||||||
bmiResult = null
|
|
||||||
}
|
|
||||||
age <= 0 || age > 120 -> {
|
|
||||||
validationError = "Umur tidak wajar."
|
|
||||||
bmiResult = null
|
|
||||||
}
|
|
||||||
(useMetricUnits && (height <= 50 || height > 270)) || (!useMetricUnits && (height <= 20 || height > 108)) -> {
|
|
||||||
validationError = "Tinggi badan tidak wajar."
|
|
||||||
bmiResult = null
|
|
||||||
}
|
|
||||||
(useMetricUnits && (weight <= 20 || weight > 500)) || (!useMetricUnits && (weight <= 45 || weight > 1100)) -> {
|
|
||||||
validationError = "Berat badan tidak wajar."
|
|
||||||
bmiResult = null
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
validationError = null
|
|
||||||
bmiResult = calculateBmi(weight, height, useMetricUnits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onReset = { resetFields() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ResultDisplay(result: BmiFullResult) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
|
||||||
) {
|
|
||||||
Column(Modifier.padding(24.dp).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Text("Hasil", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
BmiGauge(bmi = result.bmi)
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
Text("BMI = ${String.format("%.1f", result.bmi)} kg/m² (${result.category})",
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(24.dp))
|
|
||||||
ResultDetails(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BmiGauge(bmi: Double) {
|
|
||||||
val minBmi = 15.0
|
|
||||||
val maxBmi = 40.0
|
|
||||||
val clampedBmi = bmi.coerceIn(minBmi, maxBmi)
|
|
||||||
val progress = if (bmi > 0) ((clampedBmi - minBmi) / (maxBmi - minBmi)).toFloat() else 0f
|
|
||||||
val angle = 180f + progress * 180f
|
|
||||||
val animatedAngle by animateFloatAsState(targetValue = angle, label = "BMI Angle")
|
|
||||||
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val gaugeWidth = with(density) { 25.dp.toPx() }
|
|
||||||
val underweightColor = Color(0xFFD32F2F)
|
|
||||||
val normalColor = MaterialTheme.colorScheme.primary
|
|
||||||
val overweightColor = Color(0xFFFFEB3B)
|
|
||||||
val obeseColor = Color(0xFFB71C1C)
|
|
||||||
val onSurfaceColor = MaterialTheme.colorScheme.onSurface
|
|
||||||
val surfaceColor = MaterialTheme.colorScheme.surface
|
|
||||||
|
|
||||||
Box(modifier = Modifier.size(280.dp).padding(20.dp), contentAlignment = Alignment.Center) {
|
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
|
||||||
val center = Offset(size.width / 2, size.height / 2)
|
|
||||||
val radius = (size.minDimension / 2) - gaugeWidth / 2
|
|
||||||
val bmiRange = maxBmi - minBmi
|
|
||||||
val underweightSweep = ((18.5 - minBmi) / bmiRange * 180f).toFloat()
|
|
||||||
val normalSweep = ((25.0 - 18.5) / bmiRange * 180f).toFloat()
|
|
||||||
val overweightSweep = ((30.0 - 25.0) / bmiRange * 180f).toFloat()
|
|
||||||
val obeseSweep = ((maxBmi - 30.0) / bmiRange * 180f).toFloat()
|
|
||||||
|
|
||||||
drawArc(underweightColor, 180f, underweightSweep, false, style = Stroke(gaugeWidth, cap = StrokeCap.Butt))
|
|
||||||
drawArc(normalColor, 180f + underweightSweep, normalSweep, false, style = Stroke(gaugeWidth, cap = StrokeCap.Butt))
|
|
||||||
drawArc(overweightColor, 180f + underweightSweep + normalSweep, overweightSweep, false, style = Stroke(gaugeWidth, cap = StrokeCap.Butt))
|
|
||||||
drawArc(obeseColor, 180f + underweightSweep + normalSweep + overweightSweep, obeseSweep, false, style = Stroke(gaugeWidth, cap = StrokeCap.Butt))
|
|
||||||
|
|
||||||
val angleRad = Math.toRadians(animatedAngle.toDouble()).toFloat()
|
|
||||||
val needleLength = radius
|
|
||||||
val endOffset = Offset(center.x + needleLength * cos(angleRad), center.y + needleLength * sin(angleRad))
|
|
||||||
|
|
||||||
drawLine(onSurfaceColor, center, endOffset, 5.dp.toPx(), StrokeCap.Round)
|
|
||||||
drawCircle(onSurfaceColor, radius = 10.dp.toPx(), center = center)
|
|
||||||
drawCircle(surfaceColor, radius = 5.dp.toPx(), center = center)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Text(text = "BMI", style = MaterialTheme.typography.bodyLarge)
|
|
||||||
Text(
|
|
||||||
text = if (bmi > 0) String.format(Locale.getDefault(), "%.1f", bmi) else "-",
|
|
||||||
style = MaterialTheme.typography.displayMedium.copy(fontWeight = FontWeight.Bold, color = onSurfaceColor)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ResultDetails(result: BmiFullResult) {
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Text("Rentang BMI sehat: ${result.healthyBmiRange} kg/m²", style = MaterialTheme.typography.bodyLarge)
|
|
||||||
Text("Berat badan sehat untuk tinggi Anda: ${result.healthyWeightRange}", style = MaterialTheme.typography.bodyLarge)
|
|
||||||
Text("BMI Prime: ${String.format("%.2f", result.bmiPrime)}", style = MaterialTheme.typography.bodyLarge)
|
|
||||||
Text("Indeks Ponderal: ${String.format("%.1f", result.ponderalIndex)} kg/m³", style = MaterialTheme.typography.bodyLarge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun InputPanel(
|
|
||||||
heightInput: String, onHeightChange: (String) -> Unit,
|
|
||||||
weightInput: String, onWeightChange: (String) -> Unit,
|
|
||||||
ageInput: String, onAgeChange: (String) -> Unit,
|
|
||||||
selectedGender: String, onGenderSelect: (String) -> Unit,
|
|
||||||
useMetric: Boolean, onUnitChange: (Boolean) -> Unit
|
|
||||||
) {
|
|
||||||
val heightLabel = if (useMetric) "Tinggi (cm)" else "Tinggi (in)"
|
|
||||||
val weightLabel = if (useMetric) "Berat (kg)" else "Berat (lbs)"
|
|
||||||
|
|
||||||
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
|
||||||
UnitToggle(useMetric = useMetric, onUnitChange = onUnitChange)
|
|
||||||
GenderSelector(selectedGender, onOptionSelected = { onGenderSelect(it) })
|
|
||||||
BmiTextField(label = "Umur", value = ageInput, onValueChange = onAgeChange, icon = Icons.Default.DateRange)
|
|
||||||
BmiTextField(label = heightLabel, value = heightInput, onValueChange = onHeightChange, icon = Icons.Default.SwapVert)
|
|
||||||
BmiTextField(label = weightLabel, value = weightInput, onValueChange = onWeightChange, icon = Icons.Default.LineWeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ActionButtons(onCalculate: () -> Unit, onReset: () -> Unit) {
|
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
|
||||||
Button(onClick = onCalculate, modifier = Modifier.weight(1f).height(50.dp), shape = RoundedCornerShape(16.dp)) {
|
|
||||||
Text("Hitung BMI", fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
|
||||||
}
|
|
||||||
OutlinedButton(onClick = onReset, modifier = Modifier.weight(1f).height(50.dp), shape = RoundedCornerShape(16.dp)) {
|
|
||||||
Text("Ulang", fontSize = 16.sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun UnitToggle(useMetric: Boolean, onUnitChange: (Boolean) -> Unit) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Text("Metric", color = if (useMetric) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant)
|
|
||||||
Switch(checked = !useMetric, onCheckedChange = { onUnitChange(!it) }, modifier = Modifier.padding(horizontal = 8.dp))
|
|
||||||
Text("USC", color = if (!useMetric) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GenderSelector(selectedOption: String, onOptionSelected: (String) -> Unit) {
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
|
||||||
val options = listOf("Laki-laki", "Perempuan")
|
|
||||||
options.forEach { gender ->
|
|
||||||
val isSelected = selectedOption == gender
|
|
||||||
Button(
|
|
||||||
onClick = { onOptionSelected(gender) },
|
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
contentColor = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface
|
|
||||||
),
|
),
|
||||||
modifier = Modifier.weight(1f)
|
value = amountInput,
|
||||||
) {
|
onValueChanged = { amountInput = it },
|
||||||
Icon(Icons.Default.Person, contentDescription = gender, modifier = Modifier.size(18.dp))
|
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth(),
|
||||||
Spacer(Modifier.width(8.dp))
|
)
|
||||||
Text(gender)
|
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
|
@Composable
|
||||||
fun BmiTextField(label: String, value: String, onValueChange: (String) -> Unit, icon: androidx.compose.ui.graphics.vector.ImageVector) {
|
fun EditNumberField(
|
||||||
OutlinedTextField(
|
@StringRes label: Int,
|
||||||
|
@DrawableRes leadingIcon: Int,
|
||||||
|
keyboardOptions: KeyboardOptions,
|
||||||
|
value: String,
|
||||||
|
onValueChanged: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = onValueChange,
|
singleLine = true,
|
||||||
label = { Text(label) },
|
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
|
||||||
leadingIcon = { Icon(icon, contentDescription = label) },
|
modifier = modifier,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
|
onValueChange = onValueChanged,
|
||||||
shape = RoundedCornerShape(12.dp),
|
label = { Text(stringResource(label)) },
|
||||||
modifier = Modifier.fillMaxWidth()
|
keyboardOptions = keyboardOptions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Calculation Logic ---
|
|
||||||
fun calculateBmi(weight: Double, height: Double, useMetric: Boolean): BmiFullResult? {
|
|
||||||
if (height <= 0 || weight <= 0) return null
|
|
||||||
|
|
||||||
val bmi = if (useMetric) {
|
|
||||||
val heightInMeters = height / 100
|
|
||||||
weight / heightInMeters.pow(2)
|
|
||||||
} else {
|
|
||||||
(weight * 703) / height.pow(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
val category = determineBmiCategory(bmi)
|
|
||||||
val heightInMeters = if (useMetric) height / 100 else height * 0.0254
|
|
||||||
val minHealthyWeight = 18.5 * heightInMeters.pow(2)
|
|
||||||
val maxHealthyWeight = 25.0 * heightInMeters.pow(2)
|
|
||||||
|
|
||||||
val healthyWeightRangeString = if (useMetric) {
|
|
||||||
String.format("%.1f kg - %.1f kg", minHealthyWeight, maxHealthyWeight)
|
|
||||||
} else {
|
|
||||||
val minHealthyWeightLbs = minHealthyWeight * 2.20462
|
|
||||||
val maxHealthyWeightLbs = maxHealthyWeight * 2.20462
|
|
||||||
String.format("%.1f lbs - %.1f lbs", minHealthyWeightLbs, maxHealthyWeightLbs)
|
|
||||||
}
|
|
||||||
|
|
||||||
val bmiPrime = bmi / 25.0
|
|
||||||
val weightInKg = if (useMetric) weight else weight * 0.453592
|
|
||||||
val ponderalIndex = weightInKg / heightInMeters.pow(3)
|
|
||||||
|
|
||||||
return BmiFullResult(
|
|
||||||
bmi = bmi,
|
|
||||||
category = category,
|
|
||||||
healthyWeightRange = healthyWeightRangeString,
|
|
||||||
bmiPrime = bmiPrime,
|
|
||||||
ponderalIndex = ponderalIndex
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun determineBmiCategory(bmi: Double): String {
|
|
||||||
return when {
|
|
||||||
bmi < 18.5 -> "Berat badan kurang"
|
|
||||||
bmi < 25 -> "Normal"
|
|
||||||
bmi < 30 -> "Berat badan lebih"
|
|
||||||
else -> "Obesitas"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true, widthDp = 360, heightDp = 800)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BmiCalculatorScreenPreview() {
|
fun RoundTheTipRow(
|
||||||
TipTimeTheme(darkTheme = true) {
|
roundUp: Boolean,
|
||||||
BmiCalculatorScreen()
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,67 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
// COMPLETE Material 3 Color Palette (Purple Theme)
|
val md_theme_light_primary = Color(0xFF984061)
|
||||||
|
|
||||||
// Light Theme Colors
|
|
||||||
val md_theme_light_primary = Color(0xFF6750A4)
|
|
||||||
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||||
val md_theme_light_primaryContainer = Color(0xFFEADDFF)
|
val md_theme_light_primaryContainer = Color(0xFFFFD9E2)
|
||||||
val md_theme_light_onPrimaryContainer = Color(0xFF21005D)
|
val md_theme_light_onPrimaryContainer = Color(0xFF3E001D)
|
||||||
val md_theme_light_secondary = Color(0xFF625B71)
|
val md_theme_light_secondary = Color(0xFF754B9C)
|
||||||
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||||
val md_theme_light_secondaryContainer = Color(0xFFE8DEF8)
|
val md_theme_light_secondaryContainer = Color(0xFFF1DBFF)
|
||||||
val md_theme_light_onSecondaryContainer = Color(0xFF1D192B)
|
val md_theme_light_onSecondaryContainer = Color(0xFF2D0050)
|
||||||
val md_theme_light_tertiary = Color(0xFF7D5260)
|
val md_theme_light_tertiary = Color(0xFF984060)
|
||||||
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||||
val md_theme_light_tertiaryContainer = Color(0xFFFFD8E4)
|
val md_theme_light_tertiaryContainer = Color(0xFFFFD9E2)
|
||||||
val md_theme_light_onTertiaryContainer = Color(0xFF31111D)
|
val md_theme_light_onTertiaryContainer = Color(0xFF3E001D)
|
||||||
val md_theme_light_error = Color(0xFFB3261E)
|
val md_theme_light_error = Color(0xFFBA1A1A)
|
||||||
|
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_errorContainer = Color(0xFFF9DEDC)
|
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||||
val md_theme_light_onErrorContainer = Color(0xFF410E0B)
|
val md_theme_light_background = Color(0xFFFAFCFF)
|
||||||
val md_theme_light_background = Color(0xFFFFFBFE)
|
val md_theme_light_onBackground = Color(0xFF001F2A)
|
||||||
val md_theme_light_onBackground = Color(0xFF1C1B1F)
|
val md_theme_light_surface = Color(0xFFFAFCFF)
|
||||||
val md_theme_light_surface = Color(0xFFFFFBFE)
|
val md_theme_light_onSurface = Color(0xFF001F2A)
|
||||||
val md_theme_light_onSurface = Color(0xFF1C1B1F)
|
val md_theme_light_surfaceVariant = Color(0xFFF2DDE2)
|
||||||
val md_theme_light_surfaceVariant = Color(0xFFE7E0EC)
|
val md_theme_light_onSurfaceVariant = Color(0xFF514347)
|
||||||
val md_theme_light_onSurfaceVariant = Color(0xFF49454F)
|
val md_theme_light_outline = Color(0xFF837377)
|
||||||
val md_theme_light_outline = Color(0xFF79747E)
|
val md_theme_light_inverseOnSurface = Color(0xFFE1F4FF)
|
||||||
val md_theme_light_inverseOnSurface = Color(0xFFF4EFF4)
|
val md_theme_light_inverseSurface = Color(0xFF003547)
|
||||||
val md_theme_light_inverseSurface = Color(0xFF313033)
|
val md_theme_light_inversePrimary = Color(0xFFFFB0C8)
|
||||||
val md_theme_light_inversePrimary = Color(0xFFD0BCFF)
|
val md_theme_light_surfaceTint = Color(0xFF984061)
|
||||||
val md_theme_light_surfaceTint = Color(0xFF6750A4)
|
val md_theme_light_outlineVariant = Color(0xFFD5C2C6)
|
||||||
val md_theme_light_outlineVariant = Color(0xFFCAC4D0)
|
|
||||||
val md_theme_light_scrim = Color(0xFF000000)
|
val md_theme_light_scrim = Color(0xFF000000)
|
||||||
|
|
||||||
// Dark Theme Colors
|
val md_theme_dark_primary = Color(0xFFFFB0C8)
|
||||||
val md_theme_dark_primary = Color(0xFFD0BCFF)
|
val md_theme_dark_onPrimary = Color(0xFF5E1133)
|
||||||
val md_theme_dark_onPrimary = Color(0xFF381E72)
|
val md_theme_dark_primaryContainer = Color(0xFF7B2949)
|
||||||
val md_theme_dark_primaryContainer = Color(0xFF4F378B)
|
val md_theme_dark_onPrimaryContainer = Color(0xFFFFD9E2)
|
||||||
val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF)
|
val md_theme_dark_secondary = Color(0xFFDEB7FF)
|
||||||
val md_theme_dark_secondary = Color(0xFFCCC2DC)
|
val md_theme_dark_onSecondary = Color(0xFF44196A)
|
||||||
val md_theme_dark_onSecondary = Color(0xFF332D41)
|
val md_theme_dark_secondaryContainer = Color(0xFF5C3382)
|
||||||
val md_theme_dark_secondaryContainer = Color(0xFF4A4458)
|
val md_theme_dark_onSecondaryContainer = Color(0xFFF1DBFF)
|
||||||
val md_theme_dark_onSecondaryContainer = Color(0xFFE8DEF8)
|
val md_theme_dark_tertiary = Color(0xFFFFB1C7)
|
||||||
val md_theme_dark_tertiary = Color(0xFFEFB8C8)
|
val md_theme_dark_onTertiary = Color(0xFF5E1132)
|
||||||
val md_theme_dark_onTertiary = Color(0xFF492532)
|
val md_theme_dark_tertiaryContainer = Color(0xFF7B2948)
|
||||||
val md_theme_dark_tertiaryContainer = Color(0xFF633B48)
|
val md_theme_dark_onTertiaryContainer = Color(0xFFFFD9E2)
|
||||||
val md_theme_dark_onTertiaryContainer = Color(0xFFFFD8E4)
|
val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||||
val md_theme_dark_error = Color(0xFFF2B8B5)
|
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||||
val md_theme_dark_onError = Color(0xFF601410)
|
val md_theme_dark_onError = Color(0xFF690005)
|
||||||
val md_theme_dark_errorContainer = Color(0xFF8C1D18)
|
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||||
val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC)
|
val md_theme_dark_background = Color(0xFF001F2A)
|
||||||
val md_theme_dark_background = Color(0xFF1C1B1F)
|
val md_theme_dark_onBackground = Color(0xFFBFE9FF)
|
||||||
val md_theme_dark_onBackground = Color(0xFFE6E1E5)
|
val md_theme_dark_surface = Color(0xFF001F2A)
|
||||||
val md_theme_dark_surface = Color(0xFF1C1B1F)
|
val md_theme_dark_onSurface = Color(0xFFBFE9FF)
|
||||||
val md_theme_dark_onSurface = Color(0xFFE6E1E5)
|
val md_theme_dark_surfaceVariant = Color(0xFF514347)
|
||||||
val md_theme_dark_surfaceVariant = Color(0xFF49454F)
|
val md_theme_dark_onSurfaceVariant = Color(0xFFD5C2C6)
|
||||||
val md_theme_dark_onSurfaceVariant = Color(0xFFCAC4D0)
|
val md_theme_dark_outline = Color(0xFF9E8C90)
|
||||||
val md_theme_dark_outline = Color(0xFF938F99)
|
val md_theme_dark_inverseOnSurface = Color(0xFF001F2A)
|
||||||
val md_theme_dark_inverseOnSurface = Color(0xFF1C1B1F)
|
val md_theme_dark_inverseSurface = Color(0xFFBFE9FF)
|
||||||
val md_theme_dark_inverseSurface = Color(0xFFE6E1E5)
|
val md_theme_dark_inversePrimary = Color(0xFF984061)
|
||||||
val md_theme_dark_inversePrimary = Color(0xFF6750A4)
|
val md_theme_dark_surfaceTint = Color(0xFFFFB0C8)
|
||||||
val md_theme_dark_surfaceTint = Color(0xFFD0BCFF)
|
val md_theme_dark_outlineVariant = Color(0xFF514347)
|
||||||
val md_theme_dark_outlineVariant = Color(0xFF49454F)
|
|
||||||
val md_theme_dark_scrim = Color(0xFF000000)
|
val md_theme_dark_scrim = Color(0xFF000000)
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
package com.example.tiptime.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.ui.text.font.Font
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import com.example.tiptime.R
|
|
||||||
|
|
||||||
val Poppins = FontFamily(
|
|
||||||
Font(R.font.poppins_regular, FontWeight.Normal),
|
|
||||||
Font(R.font.poppins_bold, FontWeight.Bold)
|
|
||||||
)
|
|
||||||
@ -1,41 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
|
||||||
primary = Color(0xFF00C853), // A vibrant green for primary actions
|
|
||||||
onPrimary = Color.Black,
|
|
||||||
primaryContainer = Color(0xFF009624),
|
|
||||||
onPrimaryContainer = Color.White,
|
|
||||||
secondary = Color(0xFF00BFA5), // A teal for secondary elements
|
|
||||||
onSecondary = Color.Black,
|
|
||||||
secondaryContainer = Color(0xFF008E76),
|
|
||||||
onSecondaryContainer = Color.White,
|
|
||||||
tertiary = Color(0xFF00B8D4), // A cyan for accents
|
|
||||||
onTertiary = Color.Black,
|
|
||||||
tertiaryContainer = Color(0xFF008394),
|
|
||||||
onTertiaryContainer = Color.White,
|
|
||||||
background = Color(0xFF121212), // A very dark background
|
|
||||||
onBackground = Color.White,
|
|
||||||
surface = Color(0xFF1E1E1E), // A slightly lighter surface for cards
|
|
||||||
onSurface = Color.White,
|
|
||||||
surfaceVariant = Color(0xFF424242),
|
|
||||||
onSurfaceVariant = Color.White,
|
|
||||||
outline = Color(0xFF9E9E9E),
|
|
||||||
inverseOnSurface = Color.White,
|
|
||||||
inverseSurface = Color(0xFF212121),
|
|
||||||
inversePrimary = Color(0xFF00C853),
|
|
||||||
error = Color(0xFFD32F2F),
|
|
||||||
onError = Color.White,
|
|
||||||
errorContainer = Color(0xFFB71C1C),
|
|
||||||
onErrorContainer = Color.White
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
private val LightColorScheme = lightColorScheme(
|
||||||
primary = md_theme_light_primary,
|
primary = md_theme_light_primary,
|
||||||
@ -51,8 +39,8 @@ private val LightColorScheme = lightColorScheme(
|
|||||||
tertiaryContainer = md_theme_light_tertiaryContainer,
|
tertiaryContainer = md_theme_light_tertiaryContainer,
|
||||||
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
||||||
error = md_theme_light_error,
|
error = md_theme_light_error,
|
||||||
onError = md_theme_light_onError,
|
|
||||||
errorContainer = md_theme_light_errorContainer,
|
errorContainer = md_theme_light_errorContainer,
|
||||||
|
onError = md_theme_light_onError,
|
||||||
onErrorContainer = md_theme_light_onErrorContainer,
|
onErrorContainer = md_theme_light_onErrorContainer,
|
||||||
background = md_theme_light_background,
|
background = md_theme_light_background,
|
||||||
onBackground = md_theme_light_onBackground,
|
onBackground = md_theme_light_onBackground,
|
||||||
@ -62,16 +50,62 @@ private val LightColorScheme = lightColorScheme(
|
|||||||
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
||||||
outline = md_theme_light_outline,
|
outline = md_theme_light_outline,
|
||||||
inverseOnSurface = md_theme_light_inverseOnSurface,
|
inverseOnSurface = md_theme_light_inverseOnSurface,
|
||||||
inverseSurface = md_theme_light_inverseSurface,
|
inverseSurface = md_theme_light_inverseSurface,
|
||||||
inversePrimary = md_theme_light_inversePrimary,
|
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
|
@Composable
|
||||||
fun TipTimeTheme(
|
fun TipTimeTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
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
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
|
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(
|
MaterialTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
|
|||||||
@ -1,36 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
// Set of Material typography styles to start with
|
||||||
val Typography = Typography(
|
val Typography = Typography(
|
||||||
// Override default text styles to use Poppins
|
displaySmall = TextStyle(
|
||||||
headlineMedium = TextStyle(
|
fontFamily = FontFamily.Default,
|
||||||
fontFamily = Poppins,
|
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 28.sp
|
fontSize = 36.sp,
|
||||||
),
|
lineHeight = 44.sp,
|
||||||
titleMedium = TextStyle(
|
letterSpacing = 0.sp,
|
||||||
fontFamily = Poppins,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 16.sp
|
|
||||||
),
|
|
||||||
bodyLarge = TextStyle(
|
|
||||||
fontFamily = Poppins,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 16.sp
|
|
||||||
),
|
|
||||||
bodyMedium = TextStyle(
|
|
||||||
fontFamily = Poppins,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 14.sp
|
|
||||||
),
|
|
||||||
displayMedium = TextStyle(
|
|
||||||
fontFamily = Poppins,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 57.sp
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
23
app/src/main/res/drawable/percent.xml
Normal file
23
app/src/main/res/drawable/percent.xml
Normal 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>
|
||||||
Binary file not shown.
Binary file not shown.
@ -1,14 +1,24 @@
|
|||||||
<?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">BMI Calculator</string>
|
<string name="app_name">Tip Time</string>
|
||||||
<string name="bmi_calculator_title">BMI Calculator</string>
|
<string name="calculate_tip">Calculate Tip</string>
|
||||||
<string name="age">Age</string>
|
<string name="bill_amount">Bill Amount</string>
|
||||||
<string name="height_m">Height (m/cm)</string>
|
<string name="how_was_the_service">Tip Percentage</string>
|
||||||
<string name="height_in">Height (in)</string>
|
<string name="round_up_tip">Round up tip?</string>
|
||||||
<string name="weight_kg">Weight (kg)</string>
|
<string name="tip_amount">Tip Amount: %s</string>
|
||||||
<string name="weight_lbs">Weight (lbs)</string>
|
|
||||||
<string name="si_units">SI Units</string>
|
|
||||||
<string name="usc_units">USC Units</string>
|
|
||||||
<string name="your_bmi">Your BMI</string>
|
|
||||||
<string name="awaiting_input">Awaiting input</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
// NAMA: HADI PRAKOSO
|
|
||||||
// NPM: 202310715312
|
|
||||||
|
|
||||||
package com.example.tiptime
|
|
||||||
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertNotNull
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class BmiCalculatorTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun calculateBmi_metric_normal() {
|
|
||||||
val weight = 70.0 // kg
|
|
||||||
val height = 175.0 // cm
|
|
||||||
val result = calculateBmi(weight, height, useMetric = true)
|
|
||||||
|
|
||||||
// Pastikan hasil tidak null sebelum melakukan assert pada nilainya
|
|
||||||
assertNotNull(result)
|
|
||||||
if (result != null) {
|
|
||||||
// BMI untuk 70kg dan 175cm adalah ~22.9
|
|
||||||
assertEquals(22.9, result.bmi, 0.1)
|
|
||||||
assertEquals("Normal", result.category)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun calculateBmi_usc_overweight() {
|
|
||||||
val weight = 180.0 // lbs
|
|
||||||
val height = 68.0 // inches
|
|
||||||
val result = calculateBmi(weight, height, useMetric = false)
|
|
||||||
|
|
||||||
assertNotNull(result)
|
|
||||||
if (result != null) {
|
|
||||||
assertEquals(27.4, result.bmi, 0.1)
|
|
||||||
assertEquals("Berat badan lebih", result.category)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun determineBmiCategory_underweight() {
|
|
||||||
val category = determineBmiCategory(18.4)
|
|
||||||
assertEquals("Berat badan kurang", category)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun determineBmiCategory_normal() {
|
|
||||||
val category = determineBmiCategory(22.0)
|
|
||||||
assertEquals("Normal", category)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun determineBmiCategory_overweight() {
|
|
||||||
val category = determineBmiCategory(27.0)
|
|
||||||
assertEquals("Berat badan lebih", category)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun determineBmiCategory_obesity() {
|
|
||||||
val category = determineBmiCategory(31.0)
|
|
||||||
assertEquals("Obesitas", category)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun calculateBmi_zeroHeight_returnsNull() {
|
|
||||||
val result = calculateBmi(70.0, 0.0, useMetric = true)
|
|
||||||
assertEquals(null, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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.13.0" apply false
|
id("com.android.application") version "8.7.3" apply false
|
||||||
id("com.android.library") version "8.13.0" apply false
|
id("com.android.library") version "8.7.3" 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
|
||||||
}
|
}
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
3
gradlew
vendored
3
gradlew
vendored
@ -86,7 +86,8 @@ 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\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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user