diff --git a/app/src/main/java/com/example/tiptime/MainActivity.kt b/app/src/main/java/com/example/tiptime/MainActivity.kt index e5b2273..1d607a4 100644 --- a/app/src/main/java/com/example/tiptime/MainActivity.kt +++ b/app/src/main/java/com/example/tiptime/MainActivity.kt @@ -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.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 ) { - OutlinedTextField( - value = value, - onValueChange = onValueChanged, - 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 + modifier = modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically ) { - 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) + 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 = { + // LOGIKA BARU UNTUK MEMBATASI INPUT + if (it.length <= maxLength) { + onValueChanged(it) + } + }, + label = { Text(stringResource(label)) }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = imeAction + ), + singleLine = true, + modifier = Modifier.fillMaxWidth(), + isError = isError, + keyboardActions = keyboardActions ) - Text("Metric", style = MaterialTheme.typography.bodyMedium) + if (isError) { + Text( + text = errorMessage, + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(start = 16.dp, top = 4.dp) + ) + } } } } - -// --- 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() - } -} diff --git a/app/src/main/res/drawable/ic_female.png b/app/src/main/res/drawable/ic_female.png new file mode 100644 index 0000000..359d244 Binary files /dev/null and b/app/src/main/res/drawable/ic_female.png differ diff --git a/app/src/main/res/drawable/ic_male.png b/app/src/main/res/drawable/ic_male.png new file mode 100644 index 0000000..6d79956 Binary files /dev/null and b/app/src/main/res/drawable/ic_male.png differ