login
This commit is contained in:
parent
f4cbea5b6c
commit
502ae78685
223
app/src/main/java/com/example/kalkulatorbmi/LoginScreen.kt
Normal file
223
app/src/main/java/com/example/kalkulatorbmi/LoginScreen.kt
Normal file
@ -0,0 +1,223 @@
|
||||
package com.example.kalkulatorbmi
|
||||
|
||||
// 1. TAMBAHKAN IMPORT INI untuk mengakses resource seperti gambar
|
||||
import com.example.kalkulatorbmi.R
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
// 2. Rapikan import yang duplikat
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun LoginScreen(navController: NavController) {
|
||||
var username by remember { mutableStateOf("") }
|
||||
var password by remember { mutableStateOf("") }
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
|
||||
// State untuk validasi error
|
||||
var usernameError by remember { mutableStateOf<String?>(null) }
|
||||
var passwordError by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
// State untuk animasi
|
||||
var isFormVisible by remember { mutableStateOf(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var shakeState by remember { mutableStateOf(false) }
|
||||
|
||||
// Animasi scale untuk efek "pop in" pada logo
|
||||
val scale = animateFloatAsState(
|
||||
targetValue = if (isFormVisible) 1f else 0.5f,
|
||||
animationSpec = tween(durationMillis = 500), label = "logo_scale"
|
||||
).value
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
isFormVisible = true
|
||||
}
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
// 3. Latar Belakang Gradien
|
||||
val gradientBrush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.2f),
|
||||
MaterialTheme.colorScheme.background
|
||||
)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(gradientBrush),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Animasi Logo dengan efek scale
|
||||
Image(
|
||||
// Kode ini sekarang akan berfungsi karena import R sudah ditambahkan
|
||||
painter = painterResource(id = R.drawable.ic_launcher_foreground),
|
||||
contentDescription = "Logo Aplikasi",
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.scale(scale)
|
||||
)
|
||||
Text(text = "Selamat Datang", style = MaterialTheme.typography.headlineMedium)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
// 2. Animasi Goyang saat Error
|
||||
val cardScale = animateFloatAsState(
|
||||
targetValue = if (shakeState) 1.05f else 1f,
|
||||
animationSpec = tween(100), label = "card_shake"
|
||||
).value
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isFormVisible,
|
||||
enter = fadeIn(animationSpec = tween(1000, delayMillis = 500)) +
|
||||
slideInVertically(
|
||||
initialOffsetY = { it / 2 },
|
||||
animationSpec = tween(1000, delayMillis = 500)
|
||||
)
|
||||
) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.scale(cardScale) // Terapkan animasi goyang
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
OutlinedTextField(
|
||||
value = username,
|
||||
onValueChange = {
|
||||
username = it
|
||||
usernameError = null // Hapus error saat user mulai mengetik
|
||||
},
|
||||
label = { Text("Username") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = usernameError != null, // Tampilkan error
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) })
|
||||
)
|
||||
// 1. Tampilkan pesan error di bawah field
|
||||
if (usernameError != null) {
|
||||
Text(
|
||||
text = usernameError!!,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(if (usernameError != null) 8.dp else 16.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = {
|
||||
password = it
|
||||
passwordError = null // Hapus error saat user mulai mengetik
|
||||
},
|
||||
label = { Text("Password") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
isError = passwordError != null, // Tampilkan error
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
trailingIcon = {
|
||||
val description = if (passwordVisible) "Sembunyikan password" else "Tampilkan password"
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
}
|
||||
}
|
||||
)
|
||||
// Tampilkan pesan error di bawah field
|
||||
if (passwordError != null) {
|
||||
Text(
|
||||
text = passwordError!!,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
var hasError = false
|
||||
if (username.isBlank()) {
|
||||
usernameError = "Username tidak boleh kosong"
|
||||
hasError = true
|
||||
}
|
||||
if (password.isBlank()) {
|
||||
passwordError = "Password tidak boleh kosong"
|
||||
hasError = true
|
||||
}
|
||||
if (hasError) {
|
||||
// Jalankan animasi goyang
|
||||
coroutineScope.launch {
|
||||
shakeState = true
|
||||
delay(100)
|
||||
shakeState = false
|
||||
}
|
||||
} else {
|
||||
isLoading = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp),
|
||||
enabled = !isLoading
|
||||
) {
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(24.dp), color = MaterialTheme.colorScheme.onPrimary)
|
||||
} else {
|
||||
Text("Login")
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
LaunchedEffect(Unit) {
|
||||
delay(1500)
|
||||
navController.navigate("main") {
|
||||
popUpTo("login") { inclusive = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user