This commit is contained in:
HagaDalpintoGinting 2025-11-07 20:28:46 +07:00
parent 177e736182
commit 577eb9f63d
3 changed files with 60 additions and 276 deletions

View File

@ -1,298 +1,82 @@
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.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.semantics.error
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.tiptime.ui.theme.TipTimeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
TipTimeTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NavigationController()
}
}
}
}
}
// Enum dan NavigationController tetap sama
private sealed class Screen {
object Start : Screen()
object Main : Screen()
}
// --- Composable InputWithImage DIPERBARUI dengan Batas Karakter ---
@Composable
private fun NavigationController() {
var currentScreen by remember { mutableStateOf<Screen>(Screen.Start) }
when (currentScreen) {
is Screen.Start -> {
StartScreen(onNavigateToMain = { currentScreen = Screen.Main })
}
is Screen.Main -> {
BMICalculatorScreen()
}
}
}
// --- INI ADALAH TAMPILAN BARU YANG LEBIH MENARIK ---
@Composable
fun BMICalculatorScreen() {
var heightInput by remember { mutableStateOf("") }
var weightInput by remember { mutableStateOf("") }
var useMetric by remember { mutableStateOf(true) }
val height = heightInput.toDoubleOrNull() ?: 0.0
val weight = weightInput.toDoubleOrNull() ?: 0.0
val bmiValue = calculateBMI(height, weight, useMetric)
val bmiCategory = bmiCategory(bmiValue)
val bmiCategoryColor = getBmiCategoryColor(bmiCategory)
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.safeDrawingPadding()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.bmi_calculator),
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(vertical = 16.dp)
)
// --- KARTU HASIL BMI ---
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(id = R.string.your_bmi_is),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = "%.1f".format(bmiValue),
fontSize = 52.sp,
fontWeight = FontWeight.Bold,
color = bmiCategoryColor
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = bmiCategory,
fontSize = 20.sp,
fontWeight = FontWeight.SemiBold,
color = bmiCategoryColor
)
Spacer(modifier = Modifier.height(16.dp))
BmiIndicatorBar(category = bmiCategory)
}
}
// --- KARTU INPUT DATA ---
Card(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
UnitSwitchRow(
isMetric = useMetric,
onUnitChanged = { useMetric = it }
)
Spacer(modifier = Modifier.height(24.dp))
val heightLabel = if (useMetric) R.string.height_cm else R.string.height_in
EditNumberField(
label = heightLabel,
leadingIcon = R.drawable.ic_height,
value = heightInput,
onValueChanged = { heightInput = it },
imeAction = ImeAction.Next
)
Spacer(modifier = Modifier.height(16.dp))
val weightLabel = if (useMetric) R.string.weight_kg else R.string.weight_lbs
EditNumberField(
label = weightLabel,
leadingIcon = R.drawable.ic_weight,
value = weightInput,
onValueChanged = { weightInput = it },
imeAction = ImeAction.Done
)
}
}
}
}
@Composable
fun BmiIndicatorBar(category: String) {
val categories = listOf("Underweight", "Normal", "Overweight", "Obese")
Row(
modifier = Modifier
.fillMaxWidth()
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface)
.padding(2.dp)
) {
categories.forEach { cat ->
Box(
modifier = Modifier
.weight(1f)
.height(12.dp)
.background(
if (cat == category) getBmiCategoryColor(cat) else Color.LightGray.copy(alpha = 0.5f)
)
) {
if (cat == category) {
Text(
text = cat,
color = Color.White,
fontSize = 8.sp, // Ukuran font sangat kecil
textAlign = TextAlign.Center,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
}
@Composable
fun EditNumberField(
fun InputWithImage(
@DrawableRes imageRes: Int,
@StringRes label: Int,
@DrawableRes leadingIcon: Int,
value: String,
onValueChanged: (String) -> Unit,
imeAction: ImeAction
isError: Boolean,
errorMessage: String,
imeAction: ImeAction,
maxLength: Int, // Parameter baru untuk batas maksimal karakter
keyboardActions: KeyboardActions = KeyboardActions.Default,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = imageRes),
contentDescription = stringResource(label),
modifier = Modifier.size(64.dp),
contentScale = ContentScale.Fit
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
OutlinedTextField(
value = value,
onValueChange = onValueChanged,
onValueChange = {
// LOGIKA BARU UNTUK MEMBATASI INPUT
if (it.length <= maxLength) {
onValueChanged(it)
}
},
label = { Text(stringResource(label)) },
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), contentDescription = null) },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = imeAction
),
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
}
@Composable
fun UnitSwitchRow(isMetric: Boolean, onUnitChanged: (Boolean) -> Unit) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = stringResource(R.string.unit_system))
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Imperial", style = MaterialTheme.typography.bodyMedium)
Switch(
checked = isMetric,
onCheckedChange = onUnitChanged,
modifier = Modifier.padding(horizontal = 8.dp)
isError = isError,
keyboardActions = keyboardActions
)
if (isError) {
Text(
text = errorMessage,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
)
Text("Metric", style = MaterialTheme.typography.bodyMedium)
}
}
}
// --- FUNGSI LOGIKA (HELPER) ---
private fun calculateBMI(height: Double, weight: Double, isMetric: Boolean): Double {
if (height <= 0 || weight <= 0) return 0.0
return if (isMetric) {
val heightInMeters = height / 100
weight / (heightInMeters * heightInMeters)
} else {
703 * weight / (height * height)
}
}
private fun bmiCategory(bmi: Double): String {
return when {
bmi == 0.0 -> "..."
bmi < 18.5 -> "Underweight"
bmi < 25.0 -> "Normal"
bmi < 30.0 -> "Overweight"
else -> "Obese"
}
}
@Composable
private fun getBmiCategoryColor(category: String): Color {
return when (category) {
"Underweight" -> Color(0xFF8AB4F8) // Biru
"Normal" -> Color(0xFF5BB974) // Hijau
"Overweight" -> Color(0xFFFDD663) // Kuning
"Obese" -> Color(0xFFE57373) // Merah
else -> MaterialTheme.colorScheme.onSurface
}
}
// --- PREVIEW ---
@Preview(showBackground = true, name = "New BMI Calculator")
@Composable
fun BMICalculatorScreenPreview() {
TipTimeTheme {
BMICalculatorScreen()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB