stefanus ayudha
07/16/2024, 12:49 PM@Composable
fun LoginScreen(
/** States **/
viewModel: LoginViewModel = viewModel(),
onSuccess: (LoginResult) -> Unit,
onNavigateToRegistration: () -> Unit,
) {
val context = LocalContext.current
viewModel.collectSideEffect {
when (it) {
is LoginScreenEffect.LoginSuccess -> onSuccess.invoke(LoginResult())
is LoginScreenEffect.ShowToastError -> Toast.makeText(
context,
it.message,
Toast.LENGTH_SHORT
).show()
}
}
Box {
Column(modifier = Modifier.fillMaxSize()) {
Text(text = "Login Screen")
val state by viewModel.collectAsState()
val showLoading by remember(state.showLoginLoading) { derivedStateOf { state.showLoginLoading } }
if (showLoading)
CircularProgressIndicator()
Spacer(modifier = Modifier.weight(1f))
val emailError by remember(state.emailError) { derivedStateOf { state.emailError } }
val passwordError by remember(state.passwordError) { derivedStateOf { state.passwordError } }
LoginForm(
emailError = emailError,
passwordError = passwordError,
onEmailInput = { viewModel.updateEmail(it) },
onPasswordInput = { viewModel.updatePassword(it) }
)
val enableButton by remember(state) { derivedStateOf { state.enableSubmitButton } }
Button(
onClick = { viewModel.submitLogin() },
enabled = enableButton
) {
Text(text = "Login")
}
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { onNavigateToRegistration.invoke() },
colors = ButtonDefaults.filledTonalButtonColors()
) {
Text(text = "Go To Registration")
}
Spacer(modifier = Modifier.weight(1f))
}
}
}
@Composable
fun LoginForm(
emailError: String,
passwordError: String,
onEmailInput: (String) -> Unit,
onPasswordInput: (String) -> Unit,
) {
Column {
var emailBuffer by remember { mutableStateOf("") }
TextField(
value = emailBuffer,
onValueChange = {
emailBuffer = it
onEmailInput.invoke(it)
},
isError = emailError.isNotBlank(),
)
if (emailError.isNotBlank())
Text(text = emailError, color = MaterialTheme.colorScheme.error)
var passwordBuffer by remember { mutableStateOf("") }
TextField(
value = passwordBuffer,
onValueChange = {
passwordBuffer = it
onPasswordInput.invoke(it)
},
isError = passwordError.isNotBlank()
)
if (passwordError.isNotBlank())
Text(text = passwordError, color = MaterialTheme.colorScheme.error)
}
}
The State:
@Immutable
@optics
data class LoginScreenSate(
val email: String = "",
val password: String = "",
val submitLoginDataState: VmState<LoginCredential> = VmIdle()
) {
companion object
val showLoginLoading: Boolean = submitLoginDataState is VmProcessing
val emailError: String
get() = when {
!email.matches(EmailPattern.toRegex()) -> "Format Salah"
else -> ""
}
val passwordError: String = when {
password.length < 10 -> "Pasword kurang panjang"
else -> ""
}
val enableSubmitButton =
emailError.isBlank() && passwordError.isBlank() && submitLoginDataState.fold(ifProcessing = { false }) { true }
}
The ViewModel
class LoginViewModel : ContainerHost<LoginScreenSate, LoginScreenEffect>, ViewModel() {
override val container: Container<LoginScreenSate, LoginScreenEffect> =
container(LoginScreenSate())
fun updateEmail(email: String) = intent {
reduce {
val lens = LoginScreenSate.email::set
lens(state, email)
}
}
fun updatePassword(password: String) = intent {
reduce {
val lens = LoginScreenSate.password
lens.set(state, password)
}
}
fun submitLogin() = intent {
val lens = LoginScreenSate.submitLoginDataState
reduce { lens.set(state, VmProcessing()) }
// do login stuff
val loginResultState = runBlocking {
delay(3000)
VmSuccess(LoginCredential())
}
// assume success
reduce { lens.set(state, loginResultState) }
postSideEffect(LoginScreenEffect.LoginSuccess(loginResultState.data))
}
}