A partir del ejercicio 0 de esta entrega Presentación colecciones 0 del Bloque 3.3, vamos a comenzar a ampliar nuestra app de colecciones para que disponga de dos pantallas: una de Login (acceso) y otra de Registro (alta de un nuevo usuario). Para ello abriremos el proyecto del ejercicio 0_presentacion_coleccion, para seguir completándolo.
Primero crearemos la parte visual de las pantallas, ambas se crearán en las funciones LoginScreen y RegistroScreen, situadas en el lugar correcto según la arquitectura explicada en temas anteriores.
Warning
No olvides crear paquetes separados para cada uno de los screen, para conseguir más claridad conforme vayamos añadiendo código durante el curso.
Como ejemplo, se proponen los siguientes aspectos para una aplicación de colecciones:
Info
No es necesario que el resultado sea exactamente igual a las capturas. Son solo una referencia: podéis personalizar textos, iconos, colores y tipografías según el tema de vuestra propia colección, siempre que se mantengan los campos y la funcionalidad que se piden.
Contendrá los siguientes elementos:
Contendrá los siguientes elementos:
Por ahora las funcionalidades de los botones, checks y enlaces no serán efectivas. Solo tendremos que implementar los eventos relacionados con la entrada de datos TextField.
Info
Para cambiar entre pantallas por ahora bastará con hacerlo manualmente desde la Main o creando dos PreViews, la navegación completa se verá en temas posteriores.
Usaremos los TextField con el tipo Validacion vistos en el tema, de la librería github.pmdmiesbalmis.components. Se mostrarán mensajes de error cuando:
Warning
Como en ejercicios anteriores, la lógica de la app (validaciones, estados, etc) se manejará fuera de los Composables que forman las screens (Main o PreView), por lo que los estados de los TextField, validacionLogin, validacionPassword, etc. deberán pasarse como parámetros a LoginScreen y RegistroScreen. Siendo aconsejable crear un ValidacionLoginUiState, Validadorlogin, además de LoginUiState, para la pantalla de Login y lo mismo para la de Registro.
La estructura de archivos dentro del feature login debería quedar de la siguiente manera.
Como ayuda para posteriores implementaciones, se adjunta el código de las partes relacionadas con la validación para los TextField del Login.
data class ValidacionLoginUiState(
val mensajeErrorGlobal: String? = null,
val validacionCorreo: Validacion = object : Validacion {},
val validacionContrasena: Validacion = object : Validacion {}
) : Validacion {
override val hayError: Boolean
get() = validacionCorreo.hayError || validacionContrasena.hayError
override val mensajeError: String?
get() = when {
validacionCorreo.hayError -> validacionCorreo.mensajeError
validacionContrasena.hayError -> validacionContrasena.mensajeError
else -> mensajeErrorGlobal
}
}
class ValidadorLogin(
var mensajeErrorGlobal: String? = "Por favor, revise los campos marcados"
) : Validador<LoginUiState> {
val validadorCorreo = ValidadorCompuesto<String>()
.add(ValidadorTextoNoVacio("El correo no puede estar vacío"))
.add(ValidadorCorreo("El correo electrónico no es válido"))
val validadorContrasena = ValidadorCompuesto<String>()
.add(ValidadorTextoNoVacio("La clave no puede estar vacía"))
.add(ValidadorLongitudMinimaTexto(6, "La clave debe tener al menos 6 caracteres"))
override fun valida(datos: LoginUiState): ValidacionLoginUiState {
val valCorreo = validadorCorreo.valida(datos.correo)
val valContrasena = validadorContrasena.valida(datos.contrasena)
return ValidacionLoginUiState(
mensajeErrorGlobal = mensajeErrorGlobal,
validacionCorreo = valCorreo,
validacionContrasena = valContrasena
)
}
}
data class LoginUiState(
val correo: String = "",
val contrasena: String = "",
val recordarSesion: Boolean = true,
val cargando: Boolean = false,
val mensajeError: String? = null,
val validacion: ValidacionLoginUiState = ValidacionLoginUiState()
)
val validadorLogin = remember { ValidadorLogin() }
var loginUiState by remember { mutableStateOf(LoginUiState()) }
LoginScreen(
modifier = Modifier.fillMaxSize(),
uiState = loginUiState,
onLoginEvent = { evento ->
when (evento) {
is LoginEvent.OnCorreoCambiado -> {
val validacionCorreo =
validadorLogin.validadorCorreo.valida(evento.correo)
loginUiState = loginUiState.copy(
correo = evento.correo,
mensajeError = null,
validacion = loginUiState.validacion.copy(validacionCorreo = validacionCorreo)
)
}
is LoginEvent.OnContrasenaCambiado -> {
val validacionContrasena =
validadorLogin.validadorContrasena.valida(evento.contrasena)
loginUiState = loginUiState.copy(
contrasena = evento.contrasena,
mensajeError = null,
validacion = loginUiState.validacion.copy(validacionContrasena = validacionContrasena)
)
}
}
}
)
Tip
El ejercicio deberá ser completado con la funcionalidad de validación para el Registro, siguiendo el esquema que se ha dado en el Login