Creación de una aplicación de aprendizaje de idiomas con Compose – Parte 4 | de Victor Brandalise | abril 2023

Bienvenido a la Parte 4 de la serie Cree una aplicación de aprendizaje de idiomas con Compose. En esta serie comparto mi progreso en la creación de una aplicación de aprendizaje de idiomas. En esta serie, comparto mi progreso, las opciones de diseño y cualquier nuevo conocimiento que obtenga en el camino. Hoy cubriré los días 13-16. Si no ha leído los primeros tres artículos, puede encontrarlos aquí: Si desea saber exactamente qué cambios de código hice, puede hacer clic en [Code Diff] enlace al lado del encabezado del día y vea todos los cambios para ese día. Lleva mucho tiempo escribir estos artículos, de ahora en adelante continuaré describiendo todo lo que hice pero solo explicaré algunas cosas. Si quieres entender cómo hice algo que no expliqué, puedes mirar el [Code Diff].Empecé el día 13 modificando la carta del mazo para que, cuando la presiones, navegue para practicar con el mazo adecuado. También agregué íconos a la carta del mazo para indicar cuántas cartas necesitas revisar (permanecer) y cuántas cartas ya has aprendido (correcto).No es un gran cambio, pero sí información bastante útil para el usuario.Progreso hasta el final del día 13. El día 14 eliminé todos los mazos falsos que tenía e hice 2 con palabras reales: «Los 20 sustantivos italianos más populares» y «Palabras para los turistas italianos». Habrá cientos de mazos en el futuro, pero por ahora estos 2 al menos harán que el ejercicio parezca más real. También agregué código para mostrar un brindis cuando el usuario presiona una carta de mazo que no tiene cartas para revisar. Para hacer esto, agregué una nueva acción a mi interfaz MyDeckListAction.sealed MyDeckListAction {…object ShowNoCardsToReviewInfo : MyDeckListAction} . Luego, en el método que navega a la práctica, verifico si hay palabras que van a review.viewModelScope.launch {val action =if (model.cardsToReview <= 0) MyDeckListAction.ShowNoCardsToReviewInfoelse MyDeckListAction.NavigateToPractice(model.deckId)_action. emit(action)} También eliminé el botón Practicar de la pantalla de inicio, no lo implementaré en un futuro cercano, por lo que no tiene ningún valor estar allí.Progreso hasta el final del día 14 Comencé el día 15 implementando una de las clases más importantes de este proyecto, la clase que verifica si una respuesta es correcta. Necesito saber un poco más que si la respuesta es correcta o incorrecta. Si es correcto, también necesito saber si es una coincidencia exacta. Para hacer esto, creé una nueva clase que representa los posibles resultados del caso de uso. Interfaz sellada CheckPracticeAnswerResponse {clase de datos correcta (val isExactAnswer: Boolean) : CheckPracticeAnswerResponseobject Wrong : CheckPracticeAnswerResponsefun isCorrect() = esto es correcto} Después de eso, definí una lista de caracteres que quiero ignorar al verificar la respuesta. Eso significa «¿cómo?» y «cómo» se tratan como si fueran la misma cosa. valor privado charsToIgnore = Regex(«[?!,.;\»‘]») Ahora llegamos al código que verifica la respuesta. Comienzo por normalizar la respuesta escrita y las posibles respuestas (salidas), esto simplemente elimina los caracteres para ignorar. Luego comparo la respuesta escrita normalizada con las posibles respuestas normalizadas Respuesta si si cualquiera de ellos coincide, el usuario ingresó la respuesta correcta, de lo contrario es la respuesta incorrecta. )if (normalizedAnswer.isBlank())return Wrongif (normalizedOutputs.any { it == normalizedAnswer })return Correct (isExactAnswer = card.outputs.any { it == answer })return Wrong}private fun String.normalize() = trim().replace(charsToIgnore, «»)private fun List
- Es un cuadro rojo si te equivocas en la respuesta.
- Es un cuadro verde si su respuesta es bastante cercana a la respuesta real.
La transición predeterminada para AnimatedVisibility expande/contrae el contenedor vertical y horizontalmente, pero solo quiero que se expanda/contraiga verticalmente, así que tuve que cambiar la transición Entrar/Salir @Composableprivate fun InfoBox(state: PracticeState) {AnimatedVisibility(visible = state.infoText != null,enter = fadeIn() + expandIn(initialSize = {IntSize(it.width, 0) }), exit = ShrinkOut(targetSize = {IntSize(it.width, 0) }) + fadeOut( ) ) { val bgColor = state.infoBackgroundColorRes ?: return@AnimatedVisibilityBox(modifier = Modifier.fillMaxWidth().background(colorResource(id = bgColor)).padding(horizontal = 12.dp, vertical = 20.dp)) {. . .. }}}Después de 15 días finalmente tenemos la primera versión «utilizable» de la aplicación.Progreso hasta el final del día 15 Si el usuario ingresa la respuesta incorrecta o algo que no es exactamente la respuesta, mostraré un cuadro de respuesta correcta a continuación. Cuando eso sucede, no quiero que la entrada esté habilitada, por lo que necesito deshabilitarla de alguna manera. También me gustaría poder usar la tecla ENTER en mi teclado para pasar a la siguiente pregunta, principalmente usaré esto en un emulador, por lo que es una función útil para mí. En realidad, no deshabilito el campo, solo ignoro los nuevos valores si no quiero que obtengan nuevos caracteres. returnstate.answer = answer}Para continuar con la siguiente pregunta cuando se presiona la tecla ENTER, tuve que agregar el modificador onKeyEvent a la respuesta input.TextField(…modifier = Modifier.onKeyEvent { onKeyEvent(it.nativeKeyEvent) } )If el evento clave es diferente de ENTER. Simplemente lo ignoro, de lo contrario, emitiré Unit a un canal. agregó que el canal es para evitar presionar la tecla Intro varias veces en un corto período de tiempo. Si echa un vistazo a continuación, elimino el evento de la tecla Intro en 50 ms para evitar que esto suceda. Originalmente no tenía este canal, pero después de algunas pruebas descubrí que el evento de la tecla ENTER estaba causando algunos problemas y esta era la razón. valor privado enterEventChannel = MutableSharedFlow
No disfrutarás de todo en lo que trabajes. De hecho, muchos de los elementos centrales de un proyecto serán las partes más difíciles que evitará como la peste. Estos «cimientos torpes» son los responsables de la muerte de una infinidad de proyectos. Son las cosas que posponemos hasta el infinito.
Cuando leí esto por primera vez, me quedé asombrado. Esto me ha pasado varias veces pero nunca he entendido por qué. Continúa proporcionando la solución:
La clave para superar estos rasgos desagradables no es la fuerza de voluntad, esta planeando. Significa decidir «aquí es donde quieres que termine este proyecto» y luego trabajar hacia atrás paso a paso para descubrir qué se debe hacer. Piensan: “Aquí están los pasos para llegar a donde quiero estar.” El objetivo no es evitar la incomodidad, es reducir la fatiga de la decisión. Tomas todas las decisiones importantes y luego simplemente sigues el plan.
Lo que me faltaba era «aquí es donde quiero que termine este proyecto». Aunque esbocé una visión al principio del proyecto, dejé de prestar atención y me perdí. Los siguientes pasos no estaban claros. Con estas actualizaciones llegamos al final de la Parte 4.Si tiene algún comentario o sugerencia, por favor póngase en contacto conmigo Gorjeo.Estén atentos para las próximas actualizaciones.Foto de William Warby en Unsplash