Compare commits

...

2 Commits

Author SHA1 Message Date
HagaDalpintoGinting
cdaf581693 update 2025-11-07 20:32:21 +07:00
HagaDalpintoGinting
577eb9f63d update 2025-11-07 20:28:46 +07:00
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.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.background import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.shape.RoundedCornerShape 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.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.* import androidx.compose.material3.OutlinedTextField
import androidx.compose.runtime.* import androidx.compose.material3.Text
import androidx.compose.runtime.getValue import androidx.compose.runtime.Composable
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.draw.clip import androidx.compose.ui.layout.ContentScale
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.text.font.FontWeight import androidx.compose.ui.semantics.error
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
private fun NavigationController() { fun InputWithImage(
var currentScreen by remember { mutableStateOf<Screen>(Screen.Start) } @DrawableRes imageRes: Int,
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,
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( Row(
modifier = Modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically
horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Text(text = stringResource(R.string.unit_system)) Image(
Row(verticalAlignment = Alignment.CenterVertically) { painter = painterResource(id = imageRes),
Text("Imperial", style = MaterialTheme.typography.bodyMedium) contentDescription = stringResource(label),
Switch( modifier = Modifier.size(64.dp),
checked = isMetric, contentScale = ContentScale.Fit
onCheckedChange = onUnitChanged, )
modifier = Modifier.padding(horizontal = 8.dp) 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()
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB