Compare commits

..

No commits in common. "cdaf58169357f5c79a868a10ce99a2fc0e9d2535" and "ade9c632ee3390795f167043204026073aa6ecc3" have entirely different histories.

3 changed files with 279 additions and 63 deletions

View File

@ -1,82 +1,298 @@
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.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.Image import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.shape.RoundedCornerShape
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.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.*
import androidx.compose.material3.Text import androidx.compose.runtime.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue
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.layout.ContentScale import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.error 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.text.style.TextAlign
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
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 @Composable
fun InputWithImage( private fun NavigationController() {
@DrawableRes imageRes: Int, 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(
@StringRes label: Int, @StringRes label: Int,
@DrawableRes leadingIcon: Int,
value: String, value: String,
onValueChanged: (String) -> Unit, onValueChanged: (String) -> Unit,
isError: Boolean, imeAction: ImeAction
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( OutlinedTextField(
value = value, value = value,
onValueChange = { onValueChange = onValueChanged,
// LOGIKA BARU UNTUK MEMBATASI INPUT
if (it.length <= maxLength) {
onValueChanged(it)
}
},
label = { Text(stringResource(label)) }, label = { Text(stringResource(label)) },
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), contentDescription = null) },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number, keyboardType = KeyboardType.Number,
imeAction = imeAction imeAction = imeAction
), ),
singleLine = true, singleLine = true,
modifier = Modifier.fillMaxWidth()
)
}
@Composable
fun UnitSwitchRow(isMetric: Boolean, onUnitChanged: (Boolean) -> Unit) {
Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
isError = isError, verticalAlignment = Alignment.CenterVertically,
keyboardActions = keyboardActions horizontalArrangement = Arrangement.SpaceBetween
) ) {
if (isError) { Text(text = stringResource(R.string.unit_system))
Text( Row(verticalAlignment = Alignment.CenterVertically) {
text = errorMessage, Text("Imperial", style = MaterialTheme.typography.bodyMedium)
color = MaterialTheme.colorScheme.error, Switch(
style = MaterialTheme.typography.bodySmall, checked = isMetric,
modifier = Modifier.padding(start = 16.dp, top = 4.dp) onCheckedChange = onUnitChanged,
modifier = Modifier.padding(horizontal = 8.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.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB