Blog Infos
Author
Published
Topics
Published
Topics

Day 13 [Code Diff]

Not a big change but pretty useful information for the user.

Progress by the end of day 13

Day 14 [Code Diff]
sealed interface MyDeckListAction {
    ...
    object ShowNoCardsToReviewInfo : MyDeckListAction
}
viewModelScope.launch {
      val action =
             if (model.cardsToReview <= 0) MyDeckListAction.ShowNoCardsToReviewInfo
             else MyDeckListAction.NavigateToPractice(model.deckId)
  
      _action.emit(action)
}

Progress by the end of day 14

Day 15 [Code Diff]
sealed interface CheckPracticeAnswerResponse {
    data class Correct(val isExactAnswer: Boolean) : CheckPracticeAnswerResponse
    object Wrong : CheckPracticeAnswerResponse
    
    fun isCorrect() = this is Correct
}

After that I defined a list of characters that I want to ignore when checking the answer. That means “How?” and “How” are treated as if they were the same thing.

private val charsToIgnore = Regex("[?!,.;\"']")
override fun checkAnswer(card: DeckCard, answer: String): CheckPracticeAnswerResponse {
    val normalizedAnswer = answer.normalize()
    val normalizedOutputs = card.outputs.normalize()

    if (normalizedAnswer.isBlank())
        return Wrong
    if (normalizedOutputs.any { it == normalizedAnswer })
        return Correct(isExactAnswer = card.outputs.any { it == answer })

    return Wrong
}

private fun String.normalize() = trim().replace(charsToIgnore, "")
private fun List<String>.normalize() = map { it.normalize() }
private val card1 = card(
    input = "Potrebbe aiutarmi, per favore?",
    outputs = listOf("Could you help me, please?"),
)

@Test
fun answerWithoutQuestionMarkIsCorrect() {
    val answer = "Could you help me, please"

    val result = useCase.checkAnswer(card1, answer)

    assertEquals(Correct(isExactAnswer = true), result)
}
  • It’s a red box if you get the answer wrong
  • It’s a green box if your answer is pretty close the the actual answer

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Meet Jewel:Create IDE plugins in Compose

Jetpack Compose is the declarative UI toolkit for Android that makes it easy to create beautiful, responsive apps. However, until recently, there was no easy way to use Compose to create IDE plugins without too…
Watch Video

Meet Jewel:Create IDE plugins in Compose

Sebastiano Poggi & Chris Sinco
UX Engineer & UX Design Lead
Google

Meet Jewel:Create IDE plugins in Compose

Sebastiano Poggi & ...
UX Engineer & UX Des ...
Google

Meet Jewel:Create IDE plugins in Compose

Sebastiano Poggi ...
UX Engineer & UX Design L ...
Google

Jobs

The default transition for AnimatedVisibility expands/collapses the container vertically and horizontally but I only want it to expand/collapse vertically so I had to change the enter/exit transition.

@Composable
private 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@AnimatedVisibility

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .background(colorResource(id = bgColor))
                .padding(horizontal = 12.dp, vertical = 20.dp)
        ) {
               ....
        }
    }
}

After 15 days, we finally have the first “usable” version of the app.

Progress by the end of day 15

 

Day 16 [Code Diff]
fun onAnswerChanged(answer: String) {
    if (!isAnswerFieldEnabled()) return
    state.answer = answer
}
TextField(
    ...
    modifier = Modifier
        .onKeyEvent { onKeyEvent(it.nativeKeyEvent) }
)

If the key event is different from ENTER I just ignore it, otherwise, I emit Unit to a channel.

fun onKeyEvent(keyEvent: NativeKeyEvent): Boolean {
    if (keyEvent.keyCode != KeyEvent.KEYCODE_ENTER) return false

    enterEventChannel.tryEmit(Unit)
    return true
}
private val enterEventChannel = MutableSharedFlow<Any>(
    extraBufferCapacity = 1,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

viewModelScope.launch {
    enterEventChannel
        .debounce(KEY_INPUT_DEBOUNCE_DELAY)
        .collect { onContinue() }
}

Finally on the onContinue method I either load the next question or check the answer based in which state the screen is on.

fun onContinue() {
    if (state.infoText != null) {
        loadNextQuestion()
    } else {
        checkAnswer()
    }
}
class CheckPracticeAnswerUseCaseImpl @Inject constructor(
    private val wordDistanceCalculator: WordDistanceCalculator
)
normalizedOutputs.forEach { output ->
    val distance = wordDistanceCalculator.calculate(normalizedAnswer, output)
    val threshold = (output.length * WORD_DIFF_THRESHOLD).toInt()
    if (distance <= threshold) return Correct(isExactAnswer = false)
}

This algorithm is not perfect but I don’t see myself changing it anymore in the near future, this is the algorithm I had in mind when I described how the practice was going to work.

Progress by the end of day 16

 

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Compose is a relatively young technology for writing declarative UI. Many developers don’t even…
READ MORE
blog
When it comes to the contentDescription-attribute, I’ve noticed a couple of things Android devs…
READ MORE
blog
In this article we’ll go through how to own a legacy code that is…
READ MORE
blog
Compose is part of the Jetpack Library released by Android last spring. Create Android…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu