Creación de una aplicación de aprendizaje de idiomas con Compose – Parte 3

Foto de Immo Wegmann en Unsplash. Bienvenido de nuevo a la Parte 3 de la serie «Crear 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 9-12. En caso de que no hayas leído los dos primeros artículos, puedes encontrarlos aquí: Si quieres saber exactamente qué cambios de código hice, puedes hacer clic en [Code Diff] enlace junto al encabezado del día y vea todos los cambios para ese día. Ahora que ya tenemos mazos, tiene sentido enumerarlos en la pantalla de inicio. El usuario solo debe ver los mazos a los que está suscrito, pero como no tenemos un usuario ni podemos suscribirnos a los mazos, solo estoy enumerando todo. Simplemente repetí el caso de uso y los cambios en el repositorio que me viste hacer antes y lo llamé desde el modelo de vista. Una cosa que vale la pena señalar es que no devolveré la entidad de mazo que creamos anteriormente. Primero déjame explicarte el problema que estaba teniendo para poder explicar por qué hice esto. Supongamos que hay una baraja de 10 palabras. El usuario A se suscribe a este mazo y aprende tres palabras, el usuario B también se suscribe a este mazo y aprende una palabra. Necesitamos salvar de alguna manera que estos diferentes usuarios aprendieron diferentes palabras, no pueden ser parte del mazo. Para resolver este problema, creé la entidad MyDeck (y luego MyCard). Estas dos entidades son representaciones de Deck o DeckCard pertenecientes a una clase user.data. MyDeck(val id: String, // es diferente de deckId, luego agregué el título deckIdval: String,val learnCards: Int,val totalCards: Int, ) Por el momento no hay un campo de ID de usuario aquí porque no tengo eso todavía construido, pero eso sucederá en el futuro. Lo que puedes ver a continuación es una artimaña.no estoy seguro de si el prefijo My es una buena opción, también pensé en SubscribedDeck, pero no estoy seguro de cómo funcionará este proyecto, así que seguiré usando el prefijo por ahora.Progreso al final del día 9 También comencé a crear la pantalla de ejercicios. El día 10, comencé a trabajar en la pantalla de práctica, que es probablemente la pantalla más importante de la aplicación. Esta es la pantalla donde los usuarios aprenden nuevas palabras y revisan las que ya han aprendido. Aquí también es donde las personas pasan la mayor parte de su tiempo en la aplicación. Yo lo llamo «práctica», pero en Doulingo se llama «lección». Cuando practicas, repites las palabras que ya has aprendido o aprendes nuevas palabras. Digamos que hay un mazo llamado 10 palabras en italiano, la primera vez que lo practiques podrás decir cinco palabras nuevas. La próxima vez que practique, obtendrá tres palabras nuevas y cinco palabras para repasar. Por ahora solo usaré palabras, por lo que funcionará así: usaré una palabra como «simio» y el usuario debe escribir «abeja». Cuando el usuario envía su respuesta, debemos verificarla. Al principio funcionará así:

  • Algunos caracteres se ignoran, por ejemplo: signos de interrogación, comas, signos de exclamación, … «abeja» y «¿abeja?» son respuestas correctas.
  • Si el usuario escribe «Ser», también quiero que sea una respuesta correcta porque es bastante similar. No estoy seguro de cuán similar todavía, pero probablemente el 85-90% de los personajes.
  • Cualquier otra cosa es una respuesta incorrecta.

Básicamente, si el usuario ingresa algo similar a la respuesta, se considera una respuesta correcta. En mi experiencia aprendiendo nuevos idiomas, no me gusta cuando cometo un error tipográfico y tengo que empezar de nuevo, así que no hago lo mismo con Lingua. Llamo a esto una sesión de práctica. Cuando el usuario abre la pantalla de práctica, el backend devuelve un objeto que contiene las palabras que se aprenderán/repasarán. Inicialmente, hay un máximo de ocho palabras. Hay tres formas de comprobar una respuesta:

  1. Devuelve las posibles respuestas desde el backend y el cliente simplemente verifica si alguna de ellas coincide. Esto no funciona porque simplemente hay docenas de opciones como mostré antes.
  2. Envía la respuesta al backend. Eso sería genial, pero aumentaría la latencia al llamar al servicio de backend y esto sería algo que causaría una mala experiencia para el usuario.
  3. Compruebe la respuesta en el sitio. Esta no es la opción ideal, pero al menos podemos dar retroalimentación al usuario casi instantáneamente, este es el enfoque que tomé.

Por ahora, acabo de crear un caso de uso para él y devolví verdadero, eso funciona ahora. class CheckPracticeAnswerUseCaseImpl @Inject constructor() : CheckPracticeAnswerUseCase {override fun checkAnswer(card: DeckCard, answer: String): Boolean {return true}}Procedí a crear la pantalla de práctica y cambié el modelo de vista para solicitar una sesión de práctica desde el backend. Quería animar la barra de progreso, así que usé animateFloatAsState. En la clase State, tengo una variable de progreso que solo refleja el progreso de la práctica.val de animateFloatAsState(targetValue = state.progress)LinearProgressIndicator(progress = progress,modifier = Modifier.fillMaxWidth()) progreso.Progreso hasta el final del día 10. Los valores en la imagen de arriba todavía están codificados, no fue hasta el día 12 que realmente comencé a llamar al backend. Como mencioné anteriormente, creé algo llamado MyCard. Esta es la entidad que almacena información sobre las cartas que has practicado. Está el cardId para que sepamos a qué tarjeta se refiere y una lista de las veces que practica esa tarjeta. Esto es útil para saber cuándo volver a revisar el mapa y luego mostrarlo en gráficos. val isCorrect: Boolean) También creé un caso de uso que se llama después de verificar una tarjeta para que los campos de práctica se actualicen. el caso de uso para actualizar el mapa divertido checkAnswer() { val isCorrect = checkPracticeAnswerUseCase.checkAnswer(card, state.answer)// No queremos bloquear el ejercicio, simplemente actívelo y olvídese de viewModelScope.launch { runCatching { PracticeCardUseCase. update(card.id, isCorrect) }.onFailure { /*todo: mostrar error no interrumpido */ }}state.progress = 1f – (1f / session.cards.size * cardsLeft.size)loadNextQuestion ()} No No usaba una barra de herramientas normal, creé una personalizada cuando cargaba mazos con títulos largos en mi barra de herramientas que abarcaba dos líneas y eso no es lo ideal. Para solucionar esto, realicé cambios muy pequeños en mi componente de texto en la barra de herramientas. Sesión de practica. La sesión de práctica debe provenir del backend, pero como aún no tengo un backend, se creará en el cliente. Para crear una sesión de práctica, empiezo pasando el mazo para revisar y cualquier MyCards. MyCard me permite saber qué tarjetas ya se han verificado.

  1. availableMyCards contiene solo las cartas que pertenecen al mazo dado, emparejo estas cartas con su próxima fecha de revisión.
  2. Si la tarjeta nunca ha sido verificada, debe verificarse lo antes posible. Es por eso que devuelvo Fecha (0). Luego compruebo si el último entrenamiento fue correcto, si no, la tarjeta debe verificarse 8 horas después del último entrenamiento. Si el último ejercicio fue correcto, solo uso un intervalo aleatorio para elegir cuándo verificar nuevamente.
  3. Una vez que tengo todas las tarjetas y sus respectivas fechas de revisión, filtro las que tienen una fecha de revisión anterior a esa fecha, lo que significa que deben revisarse nuevamente. Finalmente obtengo los primeros ocho ordenados por tiempo de revisión.

fun create(mazo: Mazo, misCartas: Lista): ¿Sesión de practica? {val availableMyCards = mazo.cards // #1.map { card ->card to myCards.find { it.cardId == card.id }}.mapToNextReviewDate()val now = Date()val sortedCards = availableMyCards // # 3.filtro { (_, revisión) -> revisión <= now }.sortedBy { (_, review) -> revisar.tiempo }.map { it.first }.take(8)if (sortedCards.isEmpty()) return nullreturn PracticeSession(id = UUID.randomUUID().toString(),title = mazo.title,cards = sortedCards) } lista de diversión privada>.mapToNextReviewDate(): Lista> {return map { (card, myCard) -> card to getNextReviewDate(card, myCard) }}// #2fun getNextReviewDate(card: DeckCard, myCard: MyCard?): Fecha {// Si la tarjeta nunca se revisó, ahora verifíquelo if (myCard == null || myCard.practices.isEmpty()) return Date(0)val Practices = myCard.practices.sortedBy { it.date }// Si el usuario se equivocó en la última respuesta, verifíquelo 8 horas después del valor de entrenamiento lastPractice =practices.last()if (!lastPractice.isCorrect) return lastPractice.date.plusHours(8)// Si el último usuario hizo un giro correcto, solo verifíquelo en unas pocas horas. Esto está simplificado para nowreturn Date().plusHours(Random.nextInt(12, 40))}. El GIF a continuación muestra una práctica real que se devuelve y se muestra al usuario.Progreso al final del día 12

Deja una respuesta

Tu dirección de correo electrónico no será publicada.