diff --git a/app/components/src/androidTest/java/cc/wordview/app/components/ui/WordCardTest.kt b/app/components/src/androidTest/java/cc/wordview/app/components/ui/WordCardTest.kt
deleted file mode 100644
index 158f092e..00000000
--- a/app/components/src/androidTest/java/cc/wordview/app/components/ui/WordCardTest.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package cc.wordview.app.components.ui
-
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import org.junit.Test
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-
-class WordCardTest : ComposeTest() {
- private val onClick = mock(Runnable::class.java)
-
- private fun setup(onClick: Runnable) {
- composeTestRule.setContent {
- WordCard(text = "Hello", onClick = { onClick.run() })
- }
- }
-
- @Test
- fun press() {
- setup(onClick = onClick)
-
- composeTestRule.onNodeWithText("Hello")
- .assertExists()
- .assertIsDisplayed()
- .performClick()
-
- verify(onClick).run()
- }
-}
\ No newline at end of file
diff --git a/app/components/src/main/java/cc/wordview/app/components/ui/WordButton.kt b/app/components/src/main/java/cc/wordview/app/components/ui/WordButton.kt
deleted file mode 100644
index 86b963e5..00000000
--- a/app/components/src/main/java/cc/wordview/app/components/ui/WordButton.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package cc.wordview.app.components.ui
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Card
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-/**
- * A composable function that displays a button with customizable text content.
- *
- * @param text A composable function that defines the content to be displayed inside the button.
- * @param onClick The callback invoked when the button is clicked.
- * @param modifier The [Modifier] to be applied to the button for layout customization. Defaults to an empty [Modifier].
- * @param enabled Whether the button is clickable. Defaults to true.
- */
-@Composable
-fun WordButton(text: @Composable () -> Unit, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true) {
- Card(
- modifier = modifier.width(420.dp),
- onClick = onClick,
- shape = RoundedCornerShape(20.dp),
- enabled = enabled,
- ) {
- Box(Modifier.padding(vertical = 12.dp).align(Alignment.CenterHorizontally)) {
- text()
- }
- }
-}
\ No newline at end of file
diff --git a/app/components/src/main/java/cc/wordview/app/components/ui/WordCard.kt b/app/components/src/main/java/cc/wordview/app/components/ui/WordCard.kt
deleted file mode 100644
index 25c19db6..00000000
--- a/app/components/src/main/java/cc/wordview/app/components/ui/WordCard.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package cc.wordview.app.components.ui
-
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Card
-import androidx.compose.material3.MaterialTheme.typography
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-/**
- * A composable function that displays a card containing text with customizable click behavior.
- *
- * @param text The text to be displayed inside the card.
- * @param onClick The callback invoked when the card is clicked.
- * @param modifier The [Modifier] to be applied to the card for layout customization. Defaults to an empty [Modifier].
- * @param enabled Whether the card is clickable. Defaults to true.
- */
-@Composable
-fun WordCard(text: String, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true) {
- Card(
- modifier = modifier,
- onClick = onClick,
- shape = RoundedCornerShape(20.dp),
- enabled = enabled,
- ) {
- Text(
- text = text,
- modifier = Modifier.padding(horizontal = 15.dp, vertical = 10.dp),
- style = typography.titleLarge,
- softWrap = false
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/androidTest/java/cc/wordview/app/RepositoryTestModule.kt b/app/src/androidTest/java/cc/wordview/app/RepositoryTestModule.kt
index 83f79b16..82388d90 100644
--- a/app/src/androidTest/java/cc/wordview/app/RepositoryTestModule.kt
+++ b/app/src/androidTest/java/cc/wordview/app/RepositoryTestModule.kt
@@ -25,14 +25,8 @@ import cc.wordview.app.ui.screens.player.MockPlayerRepositoryImpl
import cc.wordview.app.ui.activities.player.viewmodel.PlayerRepository
import cc.wordview.app.ui.screens.search.MockSearchRepositoryImpl
import cc.wordview.app.ui.activities.home.composables.search.SearchRepository
-import cc.wordview.app.ui.activities.lesson.viewmodel.SaveKnownWordsRepository
-import cc.wordview.app.ui.activities.lesson.viewmodel.TranslationsRepository
-import cc.wordview.app.ui.activities.player.viewmodel.KnownWordsRepository
import cc.wordview.app.ui.screens.home.MockHomeRepositoryImpl
-import cc.wordview.app.ui.screens.lesson.MockSaveKnownWordsRepositoryImpl
-import cc.wordview.app.ui.screens.lesson.MockTranslationsRepositoryImpl
import cc.wordview.app.ui.screens.login.MockLoginRepositoryImpl
-import cc.wordview.app.ui.screens.player.MockKnownWordsRepositoryImpl
import cc.wordview.app.ui.screens.register.MockRegisterRepositoryImpl
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
@@ -52,11 +46,6 @@ abstract class RepositoryTestModule {
@Binds
abstract fun bindsMockPlayerRepository(mockPlayerRepositoryImpl: MockPlayerRepositoryImpl): PlayerRepository
- @Singleton
- @Binds
- abstract fun bindsMockKnownWordsRepository(mockKnownWordsRepositoryImpl: MockKnownWordsRepositoryImpl): KnownWordsRepository
-
-
@Singleton
@Binds
abstract fun bindsMockLoginRepository(mockLoginRepositoryImpl: MockLoginRepositoryImpl): LoginRepository
@@ -68,12 +57,4 @@ abstract class RepositoryTestModule {
@Singleton
@Binds
abstract fun bindsMockHomeRepository(mockHomeRepositoryImpl: MockHomeRepositoryImpl): HomeRepository
-
- @Singleton
- @Binds
- internal abstract fun bindMockSaveKnownWordsRepository(mockSaveKnownWordsRepositoryImpl: MockSaveKnownWordsRepositoryImpl): SaveKnownWordsRepository
-
- @Singleton
- @Binds
- internal abstract fun bindMockTranslationsRepository(mockTranslationsRepositoryImpl: MockTranslationsRepositoryImpl): TranslationsRepository
}
\ No newline at end of file
diff --git a/app/src/androidTest/java/cc/wordview/app/components/TranslateResultContainerTest.kt b/app/src/androidTest/java/cc/wordview/app/components/TranslateResultContainerTest.kt
deleted file mode 100644
index c5a23b1d..00000000
--- a/app/src/androidTest/java/cc/wordview/app/components/TranslateResultContainerTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.components
-
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.onNodeWithContentDescription
-import androidx.compose.ui.test.onNodeWithText
-import cc.wordview.app.ComposeTest
-import cc.wordview.app.ui.components.TranslateResultContainer
-import org.junit.Test
-
-class TranslateResultContainerTest : ComposeTest() {
- private fun setup(correct: Boolean, words: List) {
- composeTestRule.setContent {
- TranslateResultContainer(correct, words)
- }
- }
-
- @Test
- fun correct() {
- setup(true, listOf())
-
- composeTestRule.onNodeWithText("Correct!")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithText("You answered correctly! Click proceed to continue the lesson.")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithContentDescription("Correct icon")
- .assertExists()
- .assertIsDisplayed()
- }
-
- @Test
- fun wrong() {
- val words = listOf("Hello", "World")
- setup(false, words)
-
- composeTestRule.onNodeWithText("Wrong!")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithText("You wrongly translated the phrase! The correct order is:")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithContentDescription("Incorrect icon")
- .assertExists()
- .assertIsDisplayed()
-
- for (word in words) {
- composeTestRule.onNodeWithText(word).assertExists().assertIsDisplayed()
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/LessonTest.kt b/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/LessonTest.kt
deleted file mode 100644
index 09f8b9d6..00000000
--- a/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/LessonTest.kt
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.screens.lesson
-
-import androidx.compose.ui.test.assertCountEquals
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onAllNodesWithTag
-import androidx.compose.ui.test.onNodeWithTag
-import cc.wordview.app.ui.dtos.PlayerToLessonCommunicator
-import cc.wordview.app.ui.activities.lesson.LessonActivity
-import cc.wordview.app.ui.activities.lesson.LessonNav
-import cc.wordview.app.ui.activities.lesson.LessonNav.Choose
-import cc.wordview.app.ui.activities.lesson.LessonNav.IconDrag
-import cc.wordview.app.ui.activities.lesson.LessonNav.ListenIcon
-import cc.wordview.app.ui.activities.lesson.LessonNav.ListenWord
-import cc.wordview.app.ui.activities.lesson.LessonNav.Presenter
-import cc.wordview.app.ui.activities.lesson.LessonNav.WordDrag
-import cc.wordview.app.ui.activities.lesson.viewmodel.ReviseWord
-import cc.wordview.app.ui.activities.lesson.viewmodel.SaveKnownWordsRepository
-import cc.wordview.app.ui.activities.lesson.viewmodel.TranslationsRepository
-import cc.wordview.gengolex.word.Word
-import dagger.hilt.android.testing.HiltAndroidRule
-import dagger.hilt.android.testing.HiltAndroidTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import javax.inject.Inject
-
-@HiltAndroidTest
-class LessonTest {
- @get:Rule(order = 0)
- val hiltRule = HiltAndroidRule(this)
-
- @get:Rule(order = 1)
- val composeTestRule = createAndroidComposeRule()
-
- @Inject
- lateinit var saveKnownWordsRepository: SaveKnownWordsRepository
-
- @Inject
- lateinit var translationsRepository: TranslationsRepository
-
- init {
- PlayerToLessonCommunicator.appendWord(ReviseWord(Word("tear", "lágrima", representable = true)))
- PlayerToLessonCommunicator.appendWord(ReviseWord(Word("rain", "chuva", representable = true)))
- PlayerToLessonCommunicator.appendWord(ReviseWord(Word("hear", "ouvir", representable = true)))
- PlayerToLessonCommunicator.appendWord(ReviseWord(Word("sing", "cantar", representable = true)))
-
- LessonNav.screens = listOf(
- IconDrag, WordDrag,
- Choose, Choose,
- ListenIcon, ListenWord,
- Presenter,
- LessonNav.MeaningPresenter
- )
- }
-
- @Before
- fun setup() {
- hiltRule.inject()
- }
-
- @Test
- fun renders() {
- composeTestRule.mainClock.autoAdvance = false
-
- composeTestRule.onNodeWithTag("lesson-exercise")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithTag("meaning-presenter")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.mainClock.advanceTimeBy(2_000)
-
- composeTestRule.onNodeWithTag("word-image")
- .assertExists()
- .assertIsDisplayed()
- composeTestRule.onNodeWithTag("word")
- .assertExists()
- .assertIsDisplayed()
- composeTestRule.onNodeWithTag("translated-word")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.mainClock.advanceTimeBy(10_000)
-
-
- val choose = composeTestRule.onAllNodesWithTag("choose").fetchSemanticsNodes()
- val drag = composeTestRule.onAllNodesWithTag("drag").fetchSemanticsNodes()
- val listen = composeTestRule.onAllNodesWithTag("listen").fetchSemanticsNodes()
-
- var lesson = ""
-
- if (choose.size == 1) lesson = "choose"
- if (drag.size == 1) lesson = "drag"
- if (listen.size == 1) lesson = "listen"
-
- when (lesson) {
- "choose" -> {
- composeTestRule.onNodeWithTag("choose")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithTag("icon-item")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithTag("reveal-text")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onAllNodesWithTag("alternative")
- .assertCountEquals(4)
-
- }
- "drag" -> {
- composeTestRule.onNodeWithTag("drag")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithTag("top-word")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithTag("current")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithTag("down-word")
- .assertExists()
- .assertIsDisplayed()
- }
- "listen" -> {
- composeTestRule.onNodeWithTag("listen")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithTag("listen-button")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onAllNodesWithTag("alternative")
- .assertCountEquals(4)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/MockSaveKnownWordsRepositoryImpl.kt b/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/MockSaveKnownWordsRepositoryImpl.kt
deleted file mode 100644
index 47a52024..00000000
--- a/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/MockSaveKnownWordsRepositoryImpl.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.screens.lesson
-
-import cc.wordview.app.ui.activities.lesson.viewmodel.SaveKnownWordsRepository
-import com.android.volley.RequestQueue
-import javax.inject.Inject
-
-class MockSaveKnownWordsRepositoryImpl @Inject constructor() : SaveKnownWordsRepository {
- override var onSucceed: (String) -> Unit = {}
- override var onFail: (String, Int) -> Unit = { _: String, _: Int -> }
- override fun saveKnownWords(lang: String, words: List, jwt: String) {}
- override lateinit var queue: RequestQueue
-}
\ No newline at end of file
diff --git a/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/MockTranslationsRepositoryImpl.kt b/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/MockTranslationsRepositoryImpl.kt
deleted file mode 100644
index d1065a47..00000000
--- a/app/src/androidTest/java/cc/wordview/app/ui/screens/lesson/MockTranslationsRepositoryImpl.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.screens.lesson
-
-import cc.wordview.app.api.entity.Translation
-import cc.wordview.app.ui.activities.lesson.viewmodel.TranslationsRepository
-import com.android.volley.RequestQueue
-import javax.inject.Inject
-
-class MockTranslationsRepositoryImpl @Inject constructor() : TranslationsRepository {
- override var onSucceed: (List) -> Unit = {}
- override var onFail: (String, Int) -> Unit = { _: String, _: Int -> }
- override fun getTranslations(lang: String, words: List) {}
- override lateinit var queue: RequestQueue
-}
\ No newline at end of file
diff --git a/app/src/androidTest/java/cc/wordview/app/ui/screens/player/MockKnownWordsRepositoryImpl.kt b/app/src/androidTest/java/cc/wordview/app/ui/screens/player/MockKnownWordsRepositoryImpl.kt
index 99e29b96..8e5cdae2 100644
--- a/app/src/androidTest/java/cc/wordview/app/ui/screens/player/MockKnownWordsRepositoryImpl.kt
+++ b/app/src/androidTest/java/cc/wordview/app/ui/screens/player/MockKnownWordsRepositoryImpl.kt
@@ -17,7 +17,6 @@
package cc.wordview.app.ui.screens.player
-import cc.wordview.app.ui.activities.player.viewmodel.KnownWordsRepository
import com.android.volley.RequestQueue
import jakarta.inject.Inject
diff --git a/app/src/androidTest/java/cc/wordview/app/ui/screens/player/PlayerTest.kt b/app/src/androidTest/java/cc/wordview/app/ui/screens/player/PlayerTest.kt
index dae9c3ab..898e25b1 100644
--- a/app/src/androidTest/java/cc/wordview/app/ui/screens/player/PlayerTest.kt
+++ b/app/src/androidTest/java/cc/wordview/app/ui/screens/player/PlayerTest.kt
@@ -25,7 +25,6 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import cc.wordview.app.ui.activities.home.HomeActivity
-import cc.wordview.app.ui.activities.player.viewmodel.KnownWordsRepository
import cc.wordview.app.ui.activities.player.viewmodel.PlayerRepository
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
diff --git a/app/src/androidTest/java/cc/wordview/app/ui/screens/statistics/StatisticsTest.kt b/app/src/androidTest/java/cc/wordview/app/ui/screens/statistics/StatisticsTest.kt
deleted file mode 100644
index 41b9cd5a..00000000
--- a/app/src/androidTest/java/cc/wordview/app/ui/screens/statistics/StatisticsTest.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.screens.statistics
-
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import cc.wordview.app.ui.activities.lesson.viewmodel.ReviseWord
-import cc.wordview.app.ui.activities.statistics.StatisticsActivity
-import cc.wordview.app.ui.dtos.LessonToStatisticsCommunicator
-import cc.wordview.app.ui.dtos.PlayerToLessonCommunicator
-import cc.wordview.gengolex.word.Word
-import dagger.hilt.android.testing.HiltAndroidRule
-import dagger.hilt.android.testing.HiltAndroidTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-
-@HiltAndroidTest
-class StatisticsTest {
- @get:Rule(order = 0)
- val hiltRule = HiltAndroidRule(this)
-
- @get:Rule(order = 1)
- val composeTestRule = createAndroidComposeRule()
-
- init {
- PlayerToLessonCommunicator.appendWord(ReviseWord(Word("tear", "lágrima", representable = true)))
- PlayerToLessonCommunicator.appendWord(ReviseWord(Word("rain", "chuva", representable = true)))
- PlayerToLessonCommunicator.appendWord(ReviseWord(Word("hear", "ouvir", representable = true)))
- PlayerToLessonCommunicator.appendWord(ReviseWord(Word("sing", "cantar", representable = true)))
-
- LessonToStatisticsCommunicator.wordsLearnedAmount = 2
-
- }
-
- @Before
- fun setup() {
- hiltRule.inject()
- }
-
- @Test
- fun renders() {
- composeTestRule.onNodeWithText("+2")
- .assertExists()
- .assertIsDisplayed()
-
- composeTestRule.onNodeWithText("4")
- .assertExists()
- .assertIsDisplayed()
-
- // words
- composeTestRule.onNodeWithText("lágrima")
- .assertExists()
- .assertIsDisplayed()
- composeTestRule.onNodeWithText("chuva")
- .assertExists()
- .assertIsDisplayed()
- composeTestRule.onNodeWithText("ouvir")
- .assertExists()
- .assertIsDisplayed()
- composeTestRule.onNodeWithText("cantar")
- .assertExists()
- .assertIsDisplayed()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2a01f450..0725b4ae 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -62,24 +62,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
.
- */
-
-package cc.wordview.app.ui.activities.lesson
-
-import android.os.Bundle
-import androidx.activity.compose.BackHandler
-import androidx.activity.compose.LocalActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.activity.viewModels
-import androidx.compose.animation.Crossfade
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Timelapse
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import cc.wordview.app.components.extensions.openActivity
-import cc.wordview.app.components.extensions.setOrientationSensorPortrait
-import cc.wordview.app.components.ui.BackTopAppBar
-import cc.wordview.app.components.ui.Icon
-import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.ui.activities.WordViewActivity
-import cc.wordview.app.ui.activities.lesson.viewmodel.LessonViewModel
-import cc.wordview.app.ui.activities.statistics.StatisticsActivity
-import cc.wordview.app.ui.components.LessonQuitDialog
-import cc.wordview.app.components.ui.OneTimeEffect
-import cc.wordview.app.ui.dtos.LessonToStatisticsCommunicator
-import cc.wordview.app.ui.theme.WordViewTheme
-import cc.wordview.gengolex.Language
-import dagger.hilt.android.AndroidEntryPoint
-import me.zhanghai.compose.preference.ProvidePreferenceLocals
-
-@AndroidEntryPoint
-class LessonActivity : WordViewActivity() {
- private val viewModel: LessonViewModel by viewModels()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- viewModel.load()
-
- setOrientationSensorPortrait()
- enableEdgeToEdge()
- setContent {
- WordViewTheme {
- ProvidePreferenceLocals {
- val currentScreen by viewModel.currentScreen.collectAsStateWithLifecycle()
- val timer by viewModel.timer.collectAsStateWithLifecycle()
- val timerFinished by viewModel.timerFinished.collectAsStateWithLifecycle()
- val translations by viewModel.translations.collectAsStateWithLifecycle()
-
- val activity = LocalActivity.current!!
- val context = LocalContext.current
-
- val langTag = AppSettings.language.get()
- val language = Language.byTag(langTag)
-
- var openQuitConfirm by remember { mutableStateOf(false) }
-
- OneTimeEffect {
- viewModel.getTranslations()
- viewModel.nextWord()
-
- ReviseTimer.start(
- context = context,
- onFinish = {
- viewModel.finishTimer(language)
- },
- onTick = {
- viewModel.setFormattedTime(it)
- }
- )
- }
-
- fun leave() {
- ReviseTimer.pause()
- viewModel.cleanWords()
- activity.finish()
- }
-
- fun goToStatistics() {
- ReviseTimer.pause()
- LessonToStatisticsCommunicator.wordsLearnedAmount = viewModel.getKnownWordsAmount()
- LessonToStatisticsCommunicator.translations = translations
- viewModel.cleanWords()
- context.openActivity()
- activity.finish()
- }
-
- if (openQuitConfirm) {
- ReviseTimer.pause()
- LessonQuitDialog(
- onDismiss = {
- openQuitConfirm = false
- ReviseTimer.start(
- context = context,
- onFinish = {
- viewModel.finishTimer(language)
- },
- onTick = {
- viewModel.setFormattedTime(it)
- }
- )
- },
- onConfirm = { leave() }
- )
- }
-
- LaunchedEffect(timerFinished) {
- if (timerFinished) {
- goToStatistics()
- }
- }
-
- Scaffold(topBar = {
- BackHandler { openQuitConfirm = true }
- BackTopAppBar(title = {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.End,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(text = timer, fontSize = 20.sp)
- Icon(
- modifier = Modifier.padding(end = 12.dp, start = 6.dp),
- imageVector = Icons.Filled.Timelapse,
- )
- }
- }) { openQuitConfirm = true }
- }) { innerPadding ->
- Crossfade(
- targetState = currentScreen,
- label = "Screen switch cross fade",
- animationSpec = tween(250)
- ) {
- Box(Modifier.fillMaxSize().testTag("lesson-exercise")) {
- LessonNav.getByRoute(it)?.Composable(innerPadding)
- }
- }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/LessonNav.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/LessonNav.kt
deleted file mode 100644
index 0c57a5e4..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/LessonNav.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson
-
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.runtime.Composable
-import cc.wordview.app.ui.activities.lesson.composables.Drag
-import cc.wordview.app.ui.activities.lesson.composables.LessonMode
-import cc.wordview.app.ui.activities.lesson.composables.MeaningPresenter
-import cc.wordview.app.ui.activities.lesson.composables.Presenter
-import cc.wordview.app.ui.activities.lesson.composables.Choose
-import cc.wordview.app.ui.activities.lesson.composables.Listen
-
-@Suppress("unused", "unused")
-sealed class LessonNav(val route: String) {
- @Composable
- open fun Composable(innerPadding: PaddingValues) {
- }
-
- data object IconDrag : LessonNav("icon-drag") {
- @Composable
- override fun Composable(innerPadding: PaddingValues) {
- Drag(LessonMode.ICON)
- }
- }
-
- data object WordDrag : LessonNav("word-drag") {
- @Composable
- override fun Composable(innerPadding: PaddingValues) {
- Drag(LessonMode.WORD)
- }
- }
-
- data object Choose : LessonNav("choose") {
- @Composable
- override fun Composable(innerPadding: PaddingValues) {
- Choose()
- }
- }
-
- data object ListenWord : LessonNav("listen-word") {
- @Composable
- override fun Composable(innerPadding: PaddingValues) {
- Listen(LessonMode.WORD)
- }
- }
-
- data object ListenIcon : LessonNav("listen-icon") {
- @Composable
- override fun Composable(innerPadding: PaddingValues) {
- Listen(LessonMode.ICON)
- }
- }
-
- data object Presenter : LessonNav("presenter") {
- @Composable
- override fun Composable(innerPadding: PaddingValues) {
- Presenter()
- }
- }
-
- data object MeaningPresenter : LessonNav("meaning-presenter") {
- @Composable
- override fun Composable(innerPadding: PaddingValues) {
- MeaningPresenter()
- }
- }
-
- companion object {
- var screens = listOf(
- IconDrag, WordDrag,
- Choose, Choose, // Choose needs to be repeated 2 times to make the proportions equivalent to the Drag
- ListenIcon, ListenWord,
-
- Presenter,
- MeaningPresenter
- )
-
- fun getByRoute(route: String): LessonNav? {
- for (screen in screens) {
- if (screen.route == route) return screen
- }
-
- return null
- }
-
- fun getRandomScreen(): LessonNav {
- return screens
- .filter { s -> s.route != Presenter.route }
- .filter { s -> s.route != MeaningPresenter.route }
- .random()
- }
-
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/ReviseTimer.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/ReviseTimer.kt
deleted file mode 100644
index 0a8a02d7..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/ReviseTimer.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.os.CountDownTimer
-import cc.wordview.app.BuildConfig
-import cc.wordview.app.api.APIUrl
-import cc.wordview.app.api.getStoredJwt
-import cc.wordview.app.api.request.AuthenticatedStringRequest
-import com.android.volley.Request.Method
-import com.android.volley.RequestQueue
-import com.android.volley.toolbox.Volley
-import timber.log.Timber
-import kotlin.concurrent.thread
-
-object ReviseTimer {
- var timeRemaining = 150000L
-
- private var timer: CountDownTimer? = null
- private lateinit var queue: RequestQueue
-
- fun start(context: Context, onFinish: () -> Unit, onTick: (formattedTime: String) -> Unit) {
- queue = Volley.newRequestQueue(context)
- val jwt = getStoredJwt(context)
-
- if (timer != null) {
- Timber.w("Timer is already running; The attempt to start will be ignored")
- return
- }
-
- Timber.i("Initializing timer with ${formatMillisecondsToMS(timeRemaining)} left")
-
- timer = object : CountDownTimer(timeRemaining, 1000) {
- override fun onTick(millisUntilFinished: Long) {
- timeRemaining = millisUntilFinished
- onTick(formatMillisecondsToMS(millisUntilFinished))
-
- jwt?.let {
- val url = APIUrl("${BuildConfig.API_BASE_URL}/api/v1/user/me/lesson_time?time=$millisUntilFinished")
-
- val request = AuthenticatedStringRequest(
- url.getURL(),
- jwt,
- method = Method.PUT,
- onSuccess = {},
- onError = { message, status -> Timber.e("Failed to save lesson time: \n\tmessage=$message, status=$status") }
- )
-
- queue.add(request)
- }
- }
-
- override fun onFinish() {
- Timber.i("Timer finished!")
- onFinish()
- }
- }
-
- thread { timer?.start() }
- }
-
- fun pause() {
- Timber.i("Pausing timer with ${formatMillisecondsToMS(timeRemaining)} left")
- timer?.cancel()
- timer = null
- }
-
- @SuppressLint("DefaultLocale")
- private fun formatMillisecondsToMS(milliseconds: Long): String {
- val totalSeconds = milliseconds / 1000
- val minutes = totalSeconds / 60
- val seconds = totalSeconds % 60
- return String.format("%d:%02d", minutes, seconds)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Choose.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Choose.kt
deleted file mode 100644
index 1cc0290c..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Choose.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-package cc.wordview.app.ui.activities.lesson.composables
-
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.scale
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import cc.wordview.app.components.extensions.random
-import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.ui.activities.lesson.LessonNav
-import cc.wordview.app.ui.activities.lesson.viewmodel.Answer
-import cc.wordview.app.ui.activities.lesson.viewmodel.LessonViewModel
-import cc.wordview.app.components.ui.OneTimeEffect
-import cc.wordview.app.components.ui.Space
-import cc.wordview.app.components.ui.WordCard
-import cc.wordview.app.ui.theme.Typography
-import cc.wordview.gengolex.Language
-import cc.wordview.gengolex.word.Word
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-
-@Composable
-fun Choose(lessonViewModel: LessonViewModel = hiltViewModel()) {
- val currentWord by lessonViewModel.currentWord.collectAsStateWithLifecycle()
- val words by lessonViewModel.wordsToRevise.collectAsStateWithLifecycle()
-
- val alternatives = remember { arrayListOf() }
-
- val langTag = AppSettings.language.get()
- val lang = remember { Language.byTag(langTag) }
-
- var mainText by remember { mutableStateOf("") }
- var revealedText by remember { mutableStateOf(false) }
- var buttonsEnabled by remember { mutableStateOf(true) }
- var selectedWord by remember { mutableStateOf(null) }
- var isCorrect by remember { mutableStateOf(null) }
-
- val coroutineScope = rememberCoroutineScope()
-
- OneTimeEffect {
- val filteredWords = words
- .filter { w -> w.tokenWord.word != currentWord.tokenWord.word }
- .filter { w -> w.tokenWord.representable }
-
- val res = filteredWords.map { it.tokenWord }.random(3) + currentWord.tokenWord
-
- alternatives.addAll(res.shuffled())
-
- val wordLength = currentWord.tokenWord.word.length
- mainText = "_".repeat(wordLength)
- }
-
- fun correct() {
- lessonViewModel.setAnswer(Answer.CORRECT)
- currentWord.corrects++
- }
-
- fun wrong() {
- lessonViewModel.setAnswer(Answer.WRONG)
- currentWord.misses++
- }
-
- fun validate() {
- if (selectedWord?.word == currentWord.tokenWord.word) {
- correct()
- isCorrect = true
- } else {
- wrong()
- isCorrect = false
- }
- lessonViewModel.setScreen(LessonNav.Presenter.route)
- }
-
- // Animate main text reveal
- val mainTextScale by animateFloatAsState(
- targetValue = if (revealedText) 1.15f else 1f,
- animationSpec = spring(dampingRatio = 0.4f, stiffness = 350f),
- label = "mainTextScale"
- )
- val mainTextAlpha by animateFloatAsState(
- targetValue = if (revealedText) 1f else 0.85f,
- animationSpec = tween(durationMillis = 180),
- label = "mainTextAlpha"
- )
-
- // Animate WordCards fade/scale when disabled
- val wordCardAlpha: (Word) -> Float = { word ->
- if (buttonsEnabled) 1f
- else if (word == selectedWord) 1f
- else 0.3f
- }
- val wordCardScale: (Word) -> Float = { word ->
- if (buttonsEnabled) 1f
- else if (word == selectedWord) 1.10f
- else 0.95f
- }
-
- val resultScale by animateFloatAsState(
- targetValue = if (isCorrect != null) 1.15f else 1f,
- animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f),
- label = "resultScale"
- )
-
- Column(
- modifier = Modifier
- .fillMaxSize()
- .testTag("choose"),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
- ) {
- IconItem(currentWord.tokenWord, "icon-item")
-
- Text(
- text = if (revealedText && selectedWord != null) selectedWord!!.word else mainText,
- textAlign = TextAlign.Center,
- style = if (lang == Language.JAPANESE) Typography.displayLarge else Typography.displayMedium,
- modifier = Modifier
- .scale(if (isCorrect != null) resultScale else mainTextScale)
- .alpha(if (isCorrect != null) 1f else mainTextAlpha)
- .padding(bottom = 16.dp)
- .testTag("reveal-text"),
- )
-
- Row(Modifier.padding(top = 48.dp)) {
- for (word in alternatives) {
- WordCard(
- text = word.word,
- enabled = buttonsEnabled,
- modifier = Modifier
- .scale(wordCardScale(word))
- .alpha(wordCardAlpha(word))
- .testTag("alternative"),
- onClick = {
- buttonsEnabled = false
- selectedWord = word
- revealedText = true
- // Delay for animation effect before validation
- coroutineScope.launch {
- delay(220)
- validate()
- }
- }
- )
- Space(6.dp)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Drag.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Drag.kt
deleted file mode 100644
index 67f304c9..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Drag.kt
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.composables
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.scale
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.zIndex
-import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import cc.wordview.app.components.extensions.detectTapGestures
-import cc.wordview.app.components.extensions.dragGestures
-import cc.wordview.app.ui.activities.lesson.LessonNav
-import cc.wordview.app.ui.activities.lesson.viewmodel.Answer
-import cc.wordview.app.ui.activities.lesson.viewmodel.LessonViewModel
-import cc.wordview.app.components.ui.OneTimeEffect
-import cc.wordview.gengolex.word.Word
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlin.math.roundToInt
-
-@Composable
-fun Drag(
- mode: LessonMode? = null,
- lessonViewModel: LessonViewModel = hiltViewModel()
-) {
- var offsetX by remember { mutableFloatStateOf(0f) }
- var offsetY by remember { mutableFloatStateOf(0f) }
- var isDragging by remember { mutableStateOf(false) }
- var lessonMode by remember { mutableStateOf(LessonMode.random()) }
- var isPressed by remember { mutableStateOf(false) }
-
- // Drop animation state
- var isDropAnimating by remember { mutableStateOf(false) }
- var isDropComplete by remember { mutableStateOf(false) }
- val dropAnimOffsetY = remember { Animatable(0f) }
- val dropAnimAlpha = remember { Animatable(1f) }
-
- val coroutineScope = rememberCoroutineScope()
-
- val scale by animateFloatAsState(
- targetValue = if ((isDragging || isPressed) && !isDropComplete) 0.85f else 1f,
- animationSpec = tween(durationMillis = 280)
- )
- val rotation by animateFloatAsState(
- targetValue = if (isDragging && !isDropComplete) offsetX.coerceIn(-200f, 200f) / 3f else 0f,
- animationSpec = spring(dampingRatio = 0.7f, stiffness = 300f),
- )
-
- val animatedOffsetX by animateFloatAsState(
- targetValue = if (isDragging && !isDropComplete) offsetX else 0f,
- animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f),
- )
-
- val animatedOffsetY by animateFloatAsState(
- targetValue = if (isDragging && !isDropComplete) offsetY else 0f,
- animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f),
- )
-
- val dragVisibleAlpha by animateFloatAsState(
- targetValue = if (isDropComplete) 0f else if (isDropAnimating) dropAnimAlpha.value else 1f,
- animationSpec = tween(durationMillis = 100)
- )
-
- val currentWord by lessonViewModel.currentWord.collectAsStateWithLifecycle()
- val words by lessonViewModel.wordsToRevise.collectAsStateWithLifecycle()
-
- var topWord by remember { mutableStateOf(null) }
- var downWord by remember { mutableStateOf(null) }
-
- OneTimeEffect {
- val filteredWords = words
- .filter { w -> w.tokenWord.word != currentWord.tokenWord.word }
- .filter { w -> w.tokenWord.representable }
-
- val alternatives = listOf(currentWord.tokenWord, filteredWords.random().tokenWord).shuffled()
-
- topWord = alternatives.first()
- downWord = alternatives.last()
-
- lessonMode = mode ?: LessonMode.random()
- }
-
- val alternativesAlpha by animateFloatAsState(
- targetValue = if (isDropComplete || isDropAnimating) 0.3f else 1f,
- animationSpec = tween(durationMillis = 100)
- )
-
- fun correct() {
- lessonViewModel.setAnswer(Answer.CORRECT)
- currentWord.corrects++
- }
-
- fun wrong() {
- lessonViewModel.setAnswer(Answer.WRONG)
- currentWord.misses++
- }
-
- fun onDrop(y: Float) {
- coroutineScope.launch {
- val dropTarget = when {
- y < -450 -> -800f
- y > 450 -> 800f
- else -> null
- }
- if (dropTarget != null) {
- isDropAnimating = true
- isDropComplete = false
- // Animate the drag item flying away and fading out
- dropAnimOffsetY.snapTo(y)
- dropAnimAlpha.snapTo(1f)
- launch {
- dropAnimOffsetY.animateTo(dropTarget, animationSpec = tween(durationMillis = 320))
- }
- launch {
- dropAnimAlpha.animateTo(0f, animationSpec = tween(durationMillis = 220))
- }
- delay(350)
- isDropComplete = true
- }
-
- if (y < -450) {
- if (currentWord.tokenWord.parent == topWord?.parent) correct()
- else wrong()
- lessonViewModel.setScreen(LessonNav.Presenter.route)
- }
-
- if (y > 450) {
- if (currentWord.tokenWord.parent == downWord?.parent) correct()
- else wrong()
- lessonViewModel.setScreen(LessonNav.Presenter.route)
- }
-
- // Reset drop animation
- isDropAnimating = false
- dropAnimOffsetY.snapTo(0f)
- dropAnimAlpha.snapTo(1f)
- }
- }
-
- Column(
- modifier = Modifier
- .fillMaxSize()
- .testTag("drag"),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.SpaceEvenly
- ) {
- topWord?.let {
- Box(Modifier.alpha(alternativesAlpha)) {
- when (lessonMode) {
- LessonMode.ICON -> TextItem(word = it, testTag = "top-word")
- LessonMode.WORD -> IconItem(word = it, testTag = "top-word")
- }
- }
- }
-
- Box(
- Modifier
- .offset {
- IntOffset(
- (if (isDropAnimating) 0f else animatedOffsetX).roundToInt(),
- (if (isDropAnimating) dropAnimOffsetY.value else animatedOffsetY).roundToInt()
- )
- }
- .detectTapGestures(
- onPress = {
- isPressed = true
- tryAwaitRelease()
- isPressed = false
- }
- )
- .dragGestures(
- onDragStart = {
- if (!isDropAnimating && !isDropComplete) isDragging = true
- },
- onDragEnd = {
- isDragging = false
- if (!isDropAnimating && !isDropComplete)
- onDrop(offsetY)
- offsetX = 0f
- offsetY = 0f
- },
- onDragCancel = { isDragging = false },
- onDrag = { change, dragAmount ->
- if (!isDropAnimating && !isDropComplete) {
- change.consume()
- offsetX += dragAmount.x
- offsetY += dragAmount.y
- }
- })
- .scale(scale)
- .alpha(dragVisibleAlpha)
- .zIndex(10f)
- .testTag("drag")
- .graphicsLayer {
- rotationZ = rotation
- }
- ) {
- currentWord.tokenWord.let {
- when (lessonMode) {
- LessonMode.ICON -> IconItem(word = it, testTag = "current")
- LessonMode.WORD -> TextItem(word = it, testTag = "current")
- }
- }
- }
-
- downWord?.let {
- Box(Modifier.alpha(alternativesAlpha)) {
- when (lessonMode) {
- LessonMode.ICON -> TextItem(word = it, testTag = "down-word")
- LessonMode.WORD -> IconItem(word = it, testTag = "down-word")
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Items.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Items.kt
deleted file mode 100644
index fab7f4b1..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Items.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.composables
-
-import androidx.compose.foundation.layout.size
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.misc.ImageCacheManager
-import cc.wordview.app.ui.theme.Typography
-import cc.wordview.gengolex.Language
-import cc.wordview.gengolex.word.Word
-import coil3.compose.AsyncImage
-
-@Composable
-fun TextItem(word: Word, testTag: String) {
- val langTag = AppSettings.language.get()
- val lang = remember { Language.byTag(langTag) }
-
- Text(
- modifier = Modifier.testTag(testTag),
- text = word.word,
- textAlign = TextAlign.Center,
- style = if (lang == Language.JAPANESE) Typography.displayLarge else Typography.displayMedium,
- )
-}
-
-@Composable
-fun IconItem(word: Word, testTag: String, size: Dp = 130.dp) {
- val image = ImageCacheManager.getCachedImage(word.parent)
-
- AsyncImage(
- modifier = Modifier
- .size(size)
- .testTag(testTag),
- model = image,
- contentDescription = null
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/LessonMode.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/LessonMode.kt
deleted file mode 100644
index 51e2604b..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/LessonMode.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.composables
-
-import kotlin.random.Random
-
-/**
- * Modes that some screens can be in.
- */
-enum class LessonMode {
- ICON,
- WORD;
-
- companion object {
- fun random(): LessonMode {
- val values = enumValues()
- return values[Random.nextInt(values.size)]
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Listen.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Listen.kt
deleted file mode 100644
index 7b01157b..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Listen.kt
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.composables
-
-import androidx.compose.animation.core.*
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.VolumeUp
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.scale
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import cc.wordview.app.components.extensions.random
-import cc.wordview.app.components.ui.Icon
-import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.ui.activities.lesson.LessonNav
-import cc.wordview.app.ui.activities.lesson.viewmodel.Answer
-import cc.wordview.app.ui.activities.lesson.viewmodel.LessonViewModel
-import cc.wordview.app.components.ui.OneTimeEffect
-import cc.wordview.app.components.ui.Space
-import cc.wordview.app.components.ui.WordButton
-import cc.wordview.app.ui.theme.DefaultRoundedCornerShape
-import cc.wordview.app.ui.theme.Typography
-import cc.wordview.gengolex.Language
-import cc.wordview.gengolex.word.Word
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-
-@Composable
-fun Listen(
- mode: LessonMode? = null,
- lessonViewModel: LessonViewModel = hiltViewModel()
-) {
- val currentWord by lessonViewModel.currentWord.collectAsStateWithLifecycle()
- val words by lessonViewModel.wordsToRevise.collectAsStateWithLifecycle()
-
- val alternatives = remember { arrayListOf() }
-
- val langTag = AppSettings.language.get()
- val lang = remember { Language.byTag(langTag) }
-
- var mainText by remember { mutableStateOf("") }
- var revealedText by remember { mutableStateOf(false) }
- var buttonsEnabled by remember { mutableStateOf(true) }
- var canListen by remember { mutableStateOf(true) }
- var selectedWord by remember { mutableStateOf(null) }
- var isCorrect by remember { mutableStateOf(null) }
-
- var lessonMode by remember { mutableStateOf(LessonMode.random()) }
-
- val coroutineScope = rememberCoroutineScope()
-
- OneTimeEffect {
- val filteredWords = words
- .filter { w -> w.tokenWord.word != currentWord.tokenWord.word }
- .filter { w -> w.tokenWord.representable }
-
- val res = filteredWords.map { it.tokenWord }.random(3) + currentWord.tokenWord
-
- alternatives.addAll(res.shuffled())
-
- val wordLength = currentWord.tokenWord.word.length
- mainText = "_".repeat(wordLength)
-
- lessonMode = mode ?: LessonMode.random()
- }
-
- fun correct() {
- lessonViewModel.setAnswer(Answer.CORRECT)
- currentWord.corrects++
- }
-
- fun wrong() {
- lessonViewModel.setAnswer(Answer.WRONG)
- currentWord.misses++
- }
-
- fun validate() {
- if (selectedWord?.word == currentWord.tokenWord.word) {
- correct()
- isCorrect = true
- } else {
- wrong()
- isCorrect = false
- }
- lessonViewModel.setScreen(LessonNav.Presenter.route)
- }
-
- // Animate main text reveal
- val mainTextScale by animateFloatAsState(
- targetValue = if (revealedText) 1.15f else 1f,
- animationSpec = spring(dampingRatio = 0.4f, stiffness = 350f),
- label = "mainTextScale"
- )
- val mainTextAlpha by animateFloatAsState(
- targetValue = if (revealedText) 1f else 0.85f,
- animationSpec = tween(durationMillis = 180),
- label = "mainTextAlpha"
- )
-
- // Animate WordCards fade/scale when disabled
- val wordCardAlpha: (Word) -> Float = { word ->
- if (buttonsEnabled) 1f
- else if (word == selectedWord) 1f
- else 0.3f
- }
- val wordCardScale: (Word) -> Float = { word ->
- if (buttonsEnabled) 1f
- else if (word == selectedWord) 1.10f
- else 0.95f
- }
-
- val resultScale by animateFloatAsState(
- targetValue = if (isCorrect != null) 1.15f else 1f,
- animationSpec = spring(dampingRatio = 0.5f, stiffness = 300f),
- label = "resultScale"
- )
-
- Column(
- modifier = Modifier
- .fillMaxSize()
- .testTag("listen"),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
- ) {
- Box(
- contentAlignment = Alignment.Center,
- modifier = Modifier
- .size(256.dp)
- .padding(bottom = 12.dp)
- ) {
- if (!canListen) {
- val infiniteTransition = rememberInfiniteTransition(label = "waves")
- val wave1 by infiniteTransition.animateFloat(
- initialValue = 0f,
- targetValue = 2f,
- animationSpec = infiniteRepeatable(
- animation = tween(900, easing = LinearEasing),
- repeatMode = RepeatMode.Restart
- ),
- label = "wave1"
- )
- val wave2 by infiniteTransition.animateFloat(
- initialValue = 0f,
- targetValue = 2f,
- animationSpec = infiniteRepeatable(
- animation = tween(900, 450, LinearEasing),
- repeatMode = RepeatMode.Restart
- ),
- label = "wave2"
- )
- val color = MaterialTheme.colorScheme.primary
-
- Canvas(Modifier.matchParentSize()) {
- // Outer wave 1
- drawCircle(
- color = color.copy(alpha = (1f - wave1) * 0.22f),
- radius = size.minDimension / 2 * (0.7f + wave1 * 0.7f),
- center = Offset(size.width / 2, size.height / 2)
- )
- // Outer wave 2
- drawCircle(
- color = color.copy(alpha = (1f - wave2) * 0.16f),
- radius = size.minDimension / 2 * (0.7f + wave2 * 0.7f),
- center = Offset(size.width / 2, size.height / 2)
- )
- }
- }
-
- Surface(
- modifier = Modifier.size(128.dp).testTag("listen-button"),
- color = MaterialTheme.colorScheme.surfaceContainer,
- shape = DefaultRoundedCornerShape,
- enabled = canListen,
- onClick = {
- canListen = false
-
- val toPronounce = currentWord.tokenWord.pronunciation ?: currentWord.tokenWord.word
- lessonViewModel.ttsSpeak(toPronounce, lang.locale)
-
- coroutineScope.launch {
- delay(900)
- canListen = true
- }
- }
- ) {
- Icon(
- modifier = Modifier.size(42.dp),
- imageVector = Icons.Filled.VolumeUp
- )
- }
- }
-
- Text(
- text = if (revealedText && selectedWord != null) selectedWord!!.word else mainText,
- textAlign = TextAlign.Center,
- style = if (lang == Language.JAPANESE) Typography.displayLarge else Typography.displayMedium,
- modifier = Modifier
- .scale(if (isCorrect != null) resultScale else mainTextScale)
- .alpha(if (isCorrect != null) 1f else mainTextAlpha)
- .padding(bottom = 16.dp)
- .testTag("reveal-text"),
- )
-
- Column(
- modifier = Modifier.padding(top = 48.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
- ) {
- for (word in alternatives) {
- WordButton(
- text = {
- when (lessonMode) {
- LessonMode.WORD -> {
- Text(
- text = word.word,
- style = if (lang == Language.JAPANESE) Typography.displayMedium else Typography.displaySmall,
- softWrap = false
- )
- }
-
- LessonMode.ICON -> {
- IconItem(
- word = word,
- size = 48.dp,
- testTag = "icon-item-alternative"
- )
- }
- }
- },
- enabled = buttonsEnabled,
- modifier = Modifier
- .scale(wordCardScale(word))
- .alpha(wordCardAlpha(word))
- .testTag("alternative"),
- onClick = {
- buttonsEnabled = false
- selectedWord = word
- revealedText = true
- // Delay for animation effect before validation
- coroutineScope.launch {
- delay(220)
- validate()
- }
- }
- )
- Space(12.dp)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/MeaningPresenter.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/MeaningPresenter.kt
deleted file mode 100644
index d23ce260..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/MeaningPresenter.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.composables
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.size
-import androidx.compose.material3.Text
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.dp
-import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import cc.wordview.app.R
-import cc.wordview.app.components.ui.Space
-import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.misc.ImageCacheManager
-import cc.wordview.app.ui.activities.lesson.viewmodel.LessonViewModel
-import cc.wordview.app.ui.theme.Typography
-import cc.wordview.gengolex.Language
-import coil3.compose.AsyncImage
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlin.math.roundToInt
-
-@Composable
-fun MeaningPresenter(
- lessonViewModel: LessonViewModel = hiltViewModel()
-) {
- val currentWord by lessonViewModel.currentWord.collectAsStateWithLifecycle()
- val translations by lessonViewModel.translations.collectAsStateWithLifecycle()
-
- val langTag = AppSettings.language.get()
- val lang = remember { Language.byTag(langTag) }
-
- val screenWidthPx = with(LocalDensity.current) { 400.dp.toPx() }
-
- val offsetX = remember { Animatable(-screenWidthPx) }
- val alpha = remember { Animatable(0f) }
-
- LaunchedEffect(currentWord) {
- lessonViewModel.playEffect(R.raw.discovery)
- offsetX.snapTo(-screenWidthPx)
- alpha.snapTo(0f)
-
- val slideIn = launch {
- offsetX.animateTo(
- targetValue = 0f,
- animationSpec = tween(durationMillis = 600)
- )
- }
- val fadeIn = launch {
- alpha.animateTo(
- targetValue = 1f,
- animationSpec = tween(durationMillis = 800)
- )
- }
- slideIn.join()
- fadeIn.join()
-
- val tokenWord = currentWord.tokenWord
- lessonViewModel.ttsSpeak(tokenWord.pronunciation ?: tokenWord.word, lang.locale)
- delay(2000)
-
- val slideOut = launch {
- offsetX.animateTo(
- targetValue = screenWidthPx,
- animationSpec = tween(durationMillis = 600)
- )
- }
- val fadeOut = launch {
- alpha.animateTo(
- targetValue = 0f,
- animationSpec = tween(durationMillis = 400)
- )
- }
-
- slideOut.join()
- fadeOut.join()
-
- lessonViewModel.postPresent()
- }
-
- fun getTranslated(): String {
- val parent = currentWord.tokenWord.parent
-
- var toReturn = parent
-
- for (translationEntry in translations) {
- if (translationEntry.parent == parent)
- toReturn = translationEntry.translation
- }
-
- return toReturn
- }
-
- Column(
- modifier = Modifier
- .fillMaxSize()
- .offset { IntOffset(offsetX.value.roundToInt(), 0) }
- .testTag("meaning-presenter"),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
- ) {
- val image = ImageCacheManager.getCachedImage(currentWord.tokenWord.parent)
- AsyncImage(
- modifier = Modifier
- .size(130.dp)
- .alpha(alpha.value)
- .testTag("word-image"),
- model = image,
- contentDescription = null
- )
- Space(12.dp)
- Text(
- modifier = Modifier.alpha(alpha.value).testTag("word"),
- text = currentWord.tokenWord.word,
- textAlign = TextAlign.Center,
- style = if (lang == Language.JAPANESE) Typography.displayLarge else Typography.displayMedium,
- )
- Text(
- modifier = Modifier.alpha(alpha.value).testTag("translated-word"),
- text = getTranslated(),
- textAlign = TextAlign.Center,
- style = Typography.bodyLarge,
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Presenter.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Presenter.kt
deleted file mode 100644
index f0d9fbcb..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/composables/Presenter.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.composables
-
-import androidx.compose.animation.core.EaseInOutExpo
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Cancel
-import androidx.compose.material.icons.filled.CheckCircle
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.scale
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.misc.ImageCacheManager
-import cc.wordview.app.ui.activities.lesson.viewmodel.Answer
-import cc.wordview.app.ui.activities.lesson.viewmodel.LessonViewModel
-import cc.wordview.app.ui.theme.Typography
-import cc.wordview.gengolex.Language
-import coil3.compose.AsyncImage
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlin.time.Duration.Companion.milliseconds
-
-@Composable
-fun Presenter(
- lessonViewModel: LessonViewModel = hiltViewModel()
-) {
- val answerStatus by lessonViewModel.answerStatus.collectAsStateWithLifecycle()
- val currentWord by lessonViewModel.currentWord.collectAsStateWithLifecycle()
-
- var visible by remember { mutableStateOf(false) }
-
- val langTag = AppSettings.language.get()
- LocalContext.current
-
- val scaleIn = animateFloatAsState(
- if (visible) 1f else 0.01f,
- tween(500, easing = EaseInOutExpo),
- label = "WordPresenterAnimation",
- )
-
- val fadeInOut = animateFloatAsState(
- if (visible) 1f else 0f,
- tween(250, easing = EaseInOutExpo),
- label = "WordPresenterAlpha",
- )
-
- val scope = rememberCoroutineScope()
-
- LaunchedEffect(key1 = scaleIn.value) {
- if (scaleIn.value == 1f) {
- scope.launch {
- lessonViewModel.playEffect(answerStatus)
-
- delay(1500.milliseconds)
- visible = false
- delay(500.milliseconds)
-
- if (answerStatus != Answer.NONE) {
- val answerToNextWord = answerStatus
-
- lessonViewModel.setAnswer(Answer.NONE)
- visible = true
-
- val tokenWord = currentWord.tokenWord
-
- lessonViewModel.ttsSpeak(tokenWord.pronunciation ?: tokenWord.word, Language.byTag(langTag).locale)
-
- delay(3000.milliseconds)
- visible = false
- delay(500.milliseconds)
-
- lessonViewModel.nextWord(answerToNextWord)
- }
- }
- }
- }
-
- LaunchedEffect(Unit) {
- visible = true
- }
-
- Column(
- modifier = Modifier
- .scale(scaleIn.value)
- .fillMaxSize()
- .testTag("root"),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
- ) {
- when (answerStatus) {
- Answer.CORRECT -> {
- Icon(
- modifier = Modifier
- .size(130.dp)
- .testTag("correct")
- .alpha(fadeInOut.value),
- imageVector = Icons.Filled.CheckCircle,
- contentDescription = "Correct"
- )
- }
-
- Answer.WRONG -> {
- Icon(
- modifier = Modifier
- .size(130.dp)
- .testTag("wrong")
- .alpha(fadeInOut.value),
- imageVector = Icons.Filled.Cancel,
- contentDescription = "Wrong"
- )
- }
-
- Answer.NONE -> {
- val image = ImageCacheManager.getCachedImage(currentWord.tokenWord.parent)
- AsyncImage(
- modifier = Modifier
- .size(130.dp)
- .testTag("word")
- .alpha(fadeInOut.value),
- model = image,
- contentDescription = null
- )
-
- val lang = remember { Language.byTag(langTag) }
-
- Text(
- text = currentWord.tokenWord.word,
- textAlign = TextAlign.Center,
- style = if (lang == Language.JAPANESE) Typography.displayLarge else Typography.displayMedium,
- modifier = Modifier.alpha(fadeInOut.value)
- )
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/Answer.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/Answer.kt
deleted file mode 100644
index 3d219e48..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/Answer.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.viewmodel
-
-enum class Answer {
- CORRECT, WRONG, NONE
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/LessonViewModel.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/LessonViewModel.kt
deleted file mode 100644
index 41a82f75..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/LessonViewModel.kt
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.viewmodel
-
-import android.content.Context
-import android.media.MediaPlayer
-import android.speech.tts.TextToSpeech
-import androidx.lifecycle.ViewModel
-import cc.wordview.app.R
-import cc.wordview.app.api.entity.Translation
-import cc.wordview.app.api.getStoredJwt
-import cc.wordview.app.ui.dtos.PlayerToLessonCommunicator
-import cc.wordview.app.ui.activities.lesson.LessonNav
-import cc.wordview.gengolex.Language
-import dagger.hilt.android.lifecycle.HiltViewModel
-import dagger.hilt.android.qualifiers.ApplicationContext
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.update
-import timber.log.Timber
-import java.util.Locale
-import javax.inject.Inject
-
-@HiltViewModel
-class LessonViewModel @Inject constructor(
- private val translationsRepository: TranslationsRepository,
- private val saveKnownWordsRepository: SaveKnownWordsRepository,
- @ApplicationContext private val appContext: Context
-) : ViewModel() {
- private val _currentWord = MutableStateFlow(ReviseWord())
- private val _currentScreen = MutableStateFlow("")
- private val _wordsToRevise = MutableStateFlow>(arrayListOf())
- private val _answerStatus = MutableStateFlow(Answer.NONE)
- private val _timer = MutableStateFlow("")
- private val _timerFinished = MutableStateFlow(false)
- private val _mediaPlayer = MutableStateFlow(null)
- private val _knownWords = MutableStateFlow(ArrayList())
- private val _translations = MutableStateFlow(ArrayList())
-
- private var tts: TextToSpeech? = null
-
- val currentWord = _currentWord.asStateFlow()
- val currentScreen = _currentScreen.asStateFlow()
- val answerStatus = _answerStatus.asStateFlow()
- val wordsToRevise = _wordsToRevise.asStateFlow()
- val timer = _timer.asStateFlow()
- val timerFinished = _timerFinished.asStateFlow()
- val translations = _translations.asStateFlow()
-
- fun load() {
- tts = PlayerToLessonCommunicator.tts
-
- for (word in PlayerToLessonCommunicator.wordsToRevise.value)
- appendWord(word)
- }
-
- fun nextWord(answer: Answer = Answer.NONE) {
- _wordsToRevise.update { value ->
- value.filter {
- it.tokenWord.word != currentWord.value.tokenWord.word
- } as ArrayList
- }
-
- if (currentWord.value.tokenWord.word != "") {
- when (answer) {
- Answer.CORRECT -> _wordsToRevise.value.add(
- _wordsToRevise.value.lastIndex,
- currentWord.value
- )
-
- Answer.WRONG -> _wordsToRevise.value.add(
- _wordsToRevise.value.lastIndex / 2,
- currentWord.value
- )
-
- Answer.NONE -> {}
- }
- }
-
- setWord(_wordsToRevise.value.first())
-
- Timber.d("Word '${currentWord.value.tokenWord.word}' has no phrase")
-
- if (!currentWord.value.tokenWord.representable) {
- Timber.d("Word '${currentWord.value.tokenWord.word}' is not representable (skipping)")
- nextWord(answer)
- } else {
- if (!currentWord.value.isKnown) {
- setScreen(LessonNav.MeaningPresenter.route)
- } else setScreen(LessonNav.getRandomScreen().route)
- }
- }
-
- fun postPresent() {
- currentWord.value.isKnown = true
- _knownWords.value.add(currentWord.value.tokenWord.parent)
-
- if (!currentWord.value.tokenWord.representable) {
- Timber.d("Word '${currentWord.value.tokenWord.word}' is not representable (skipping)")
- nextWord()
- } else {
- setScreen(LessonNav.getRandomScreen().route)
- }
- }
-
- fun appendWord(reviseWord: ReviseWord) {
- if (_wordsToRevise.value.contains(reviseWord)) return
-
- Timber.d("Appending '${reviseWord.tokenWord.word}' to be revised")
- _wordsToRevise.update { (it + reviseWord) as ArrayList }
- }
-
- fun setAnswer(answer: Answer) {
- _answerStatus.update { answer }
- }
-
- fun setScreen(screen: String) {
- _currentScreen.update { screen }
- }
-
- fun setWord(word: ReviseWord) {
- _currentWord.update {
- Timber.v("setWord: previous=${it.tokenWord.word} new=${word.tokenWord.word}")
- word
- }
- }
-
- fun setFormattedTime(time: String) {
- _timer.update { time }
- }
-
- fun finishTimer(language: Language) {
- saveKnownWords(language)
-
- _timerFinished.update { true }
- }
-
- fun getTranslations() {
- val words = arrayListOf()
-
- for (word in wordsToRevise.value) words.add(word.tokenWord.parent)
-
- val userLocale = appContext.resources.configuration.locales[0]
- val language = runCatching { Language.byLocaleLanguage(userLocale) }.getOrDefault(Language.ENGLISH)
-
- translationsRepository.apply {
- onSucceed = { translations ->
- _translations.update { translations as ArrayList }
- }
-
- onFail = { _: String, _: Int ->
- Timber.e("Translations request failed")
- }
-
- getTranslations(language.tag, words)
- }
- }
-
- private fun saveKnownWords(language: Language) {
- val jwt = getStoredJwt(appContext) ?: return
-
- val words = arrayListOf()
- for (word in _knownWords.value) words.add(word)
-
- saveKnownWordsRepository.apply {
- onSucceed = {
- Timber.i("Know words have been successfully saved: $it")
- }
-
- onFail = { message, status ->
- Timber.e("Failed to post known words \n\tmessage=$message, status=$status")
- }
-
- saveKnownWords(language.tag, words, jwt)
- }
- }
-
- fun cleanWords() {
- _wordsToRevise.update { arrayListOf() }
- }
-
- fun playEffect(resId: Int) {
- _mediaPlayer.value = MediaPlayer.create(appContext, resId)
- _mediaPlayer.value?.seekTo(0)
- _mediaPlayer.value?.start()
- }
-
- fun playEffect(answerStatus: Answer) {
- if (answerStatus == Answer.CORRECT) {
- playEffect(R.raw.correct)
- } else if (answerStatus == Answer.WRONG) {
- playEffect(R.raw.wrong)
- }
- }
-
- fun ttsSpeak(word: String, locale: Locale) {
- Timber.v("ttsSpeak: word=$word, locale=$locale")
-
- tts?.let { tts ->
- tts.language = locale
- tts.setSpeechRate(1.0f)
- tts.speak(
- word,
- TextToSpeech.QUEUE_ADD,
- null,
- null
- )
- }
- }
-
- fun getKnownWordsAmount() = _knownWords.value.size
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/ReviseWord.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/ReviseWord.kt
deleted file mode 100644
index 938ea0ae..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/ReviseWord.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.viewmodel
-
-import cc.wordview.gengolex.word.Word
-
-class ReviseWord(var tokenWord: Word = Word("", "")) {
- var misses = 0
- var corrects = 0
-
- /**
- * If it's the first time the user is seeing the word
- */
- var isKnown = false
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/SaveKnownWordsRepository.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/SaveKnownWordsRepository.kt
deleted file mode 100644
index 481f77df..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/SaveKnownWordsRepository.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.viewmodel
-
-import cc.wordview.app.api.ApiRequestRepository
-
-interface SaveKnownWordsRepository : ApiRequestRepository {
- var onSucceed: (String) -> Unit
- var onFail: (String, Int) -> Unit
-
- fun saveKnownWords(lang: String, words: List, jwt: String)
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/SaveKnownWordsRepositoryImpl.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/SaveKnownWordsRepositoryImpl.kt
deleted file mode 100644
index 82195ff3..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/SaveKnownWordsRepositoryImpl.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.viewmodel
-
-import android.content.Context
-import cc.wordview.app.api.APIUrl
-import cc.wordview.app.api.request.AuthenticatedStringRequest
-import com.android.volley.Request
-import com.android.volley.toolbox.Volley
-import dagger.hilt.android.qualifiers.ApplicationContext
-import org.json.JSONArray
-import org.json.JSONObject
-import javax.inject.Inject
-
-class SaveKnownWordsRepositoryImpl @Inject constructor(
- @ApplicationContext private val context: Context
-) : SaveKnownWordsRepository {
- override var onSucceed: (String) -> Unit = {}
- override var onFail: (String, Int) -> Unit = { message, status -> }
-
- override var queue = Volley.newRequestQueue(context)
-
- override fun saveKnownWords(lang: String, words: List, jwt: String) {
- val url = APIUrl("$endpoint/api/v1/lesson/words/known")
-
- val jsonArray = JSONArray()
-
- for (word in words) {
- jsonArray.put(word)
- }
-
- val json = JSONObject()
- .put("language", lang)
- .put("words", jsonArray)
-
- val request = AuthenticatedStringRequest(
- url.getURL(),
- jwt,
- Request.Method.POST,
- json,
- { onSucceed(it) },
- { message, status -> onFail(message, status) }
- )
-
- queue.add(request)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/TranslationsRepository.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/TranslationsRepository.kt
deleted file mode 100644
index 796b4241..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/TranslationsRepository.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.viewmodel
-
-import cc.wordview.app.api.ApiRequestRepository
-import cc.wordview.app.api.entity.Translation
-
-interface TranslationsRepository : ApiRequestRepository {
- var onSucceed: (List) -> Unit
- var onFail: (String, Int) -> Unit
-
- fun getTranslations(lang: String, words: List)
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/TranslationsRepositoryImpl.kt b/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/TranslationsRepositoryImpl.kt
deleted file mode 100644
index a2cc7e7b..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/lesson/viewmodel/TranslationsRepositoryImpl.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.lesson.viewmodel
-
-import android.content.Context
-import cc.wordview.app.api.APIUrl
-import cc.wordview.app.api.entity.Translation
-import cc.wordview.app.api.request.TranslationsRequest
-import com.android.volley.toolbox.Volley
-import dagger.hilt.android.qualifiers.ApplicationContext
-import org.json.JSONArray
-import org.json.JSONObject
-import javax.inject.Inject
-
-class TranslationsRepositoryImpl @Inject constructor(
- @ApplicationContext private val context: Context
-) : TranslationsRepository {
- override var onSucceed: (List) -> Unit = {}
- override var onFail: (String, Int) -> Unit = { message, status -> }
-
- override var queue = Volley.newRequestQueue(context)
-
- override fun getTranslations(lang: String, words: List) {
- val url = APIUrl("$endpoint/api/v1/lesson/translations")
-
- val array = JSONArray()
-
- for (word in words) array.put(word)
-
- val json = JSONObject()
- .put("lang", lang)
- .put("words", array)
-
- val request = TranslationsRequest(
- url.getURL(),
- json,
- { onSucceed(it) },
- { onFail("", 0) }
- )
-
- queue.add(request)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/player/PlayerActivity.kt b/app/src/main/java/cc/wordview/app/ui/activities/player/PlayerActivity.kt
index 447118b8..524911a8 100644
--- a/app/src/main/java/cc/wordview/app/ui/activities/player/PlayerActivity.kt
+++ b/app/src/main/java/cc/wordview/app/ui/activities/player/PlayerActivity.kt
@@ -38,7 +38,6 @@ import cc.wordview.app.components.extensions.setOrientationSensorLandscape
import cc.wordview.app.components.ui.CircularProgressIndicator
import cc.wordview.app.extractor.VideoStream
import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.ui.dtos.PlayerToLessonCommunicator
import cc.wordview.app.ui.activities.WordViewActivity
import cc.wordview.app.ui.activities.player.composables.ErrorScreen
import cc.wordview.app.ui.activities.player.composables.Player
@@ -89,10 +88,6 @@ class PlayerActivity : WordViewActivity() {
viewModel.initAudio(videoStream.getStreamURL())
viewModel.getLyrics(videoId, lang, videoStream)
- viewModel.getKnownWords(lang)
- viewModel.getLessonTime()
-
- PlayerToLessonCommunicator.initTts(context)
} catch (e: ExtractionException) {
Timber.e(e)
viewModel.setErrorMessage(e.message.toString())
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/player/composables/Player.kt b/app/src/main/java/cc/wordview/app/ui/activities/player/composables/Player.kt
index f83cecc5..3081314e 100644
--- a/app/src/main/java/cc/wordview/app/ui/activities/player/composables/Player.kt
+++ b/app/src/main/java/cc/wordview/app/ui/activities/player/composables/Player.kt
@@ -57,16 +57,12 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.components.extensions.openActivity
import cc.wordview.app.components.ui.CircularProgressIndicator
import cc.wordview.app.components.ui.CrossfadeIconButton
import cc.wordview.app.components.ui.FadeInAsyncImage
import cc.wordview.app.components.ui.FadeOutBox
import cc.wordview.app.extensions.getCleanUploaderName
-import cc.wordview.app.ui.activities.lesson.LessonActivity
import cc.wordview.app.ui.activities.player.viewmodel.PlayerViewModel
-import cc.wordview.app.ui.components.NoTimeLeftDialog
-import cc.wordview.app.ui.components.NotEnoughWordsDialog
import cc.wordview.app.components.ui.OneTimeEffect
import cc.wordview.app.components.ui.PlayerTopBar
import cc.wordview.app.components.ui.Seekbar
@@ -81,14 +77,11 @@ fun Player(videoId: String, viewModel: PlayerViewModel, innerPadding: PaddingVal
val playIcon by viewModel.playIcon.collectAsStateWithLifecycle()
val finalized by viewModel.finalized.collectAsStateWithLifecycle()
val isBuffering by viewModel.isBuffering.collectAsStateWithLifecycle()
- val notEnoughWords by viewModel.notEnoughWords.collectAsStateWithLifecycle()
- val noTimeLeft by viewModel.noTimeLeft.collectAsStateWithLifecycle()
val currentPosition by viewModel.currentPosition.collectAsStateWithLifecycle()
val bufferedPercentage by viewModel.bufferedPercentage.collectAsStateWithLifecycle()
val videoStream by viewModel.videoStream.collectAsStateWithLifecycle()
val activity = LocalActivity.current!!
- val context = LocalContext.current
val density = LocalDensity.current
val composerMode = AppSettings.composerMode.get()
@@ -96,8 +89,6 @@ fun Player(videoId: String, viewModel: PlayerViewModel, innerPadding: PaddingVal
LaunchedEffect(finalized) {
if (finalized) {
player.stop()
- context.openActivity()
- activity.finish()
}
}
@@ -107,10 +98,6 @@ fun Player(videoId: String, viewModel: PlayerViewModel, innerPadding: PaddingVal
}
BackHandler { back() }
-
- if (notEnoughWords) NotEnoughWordsDialog { back() }
- if (noTimeLeft) NoTimeLeftDialog { back() }
-
OneTimeEffect { player.togglePlay() }
Box(
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/KnownWordsRepository.kt b/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/KnownWordsRepository.kt
deleted file mode 100644
index 142edff0..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/KnownWordsRepository.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.player.viewmodel
-
-import cc.wordview.app.api.ApiRequestRepository
-
-interface KnownWordsRepository : ApiRequestRepository {
- var onSucceed: (List) -> Unit
- var onFail: (String, Int) -> Unit
-
- fun getKnownWords(lang: String, jwt: String)
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/KnownWordsRepositoryImpl.kt b/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/KnownWordsRepositoryImpl.kt
deleted file mode 100644
index 943e290c..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/KnownWordsRepositoryImpl.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.player.viewmodel
-
-import android.content.Context
-import cc.wordview.app.api.APIUrl
-import cc.wordview.app.api.request.AuthenticatedStringRequest
-import com.android.volley.toolbox.Volley
-import dagger.hilt.android.qualifiers.ApplicationContext
-import javax.inject.Inject
-
-class KnownWordsRepositoryImpl @Inject constructor(
- @ApplicationContext private val context: Context
-) : KnownWordsRepository {
- override var onSucceed: (List) -> Unit = { _: List -> }
- override var onFail: (String, Int) -> Unit = { message, status -> }
-
- override var queue = Volley.newRequestQueue(context)
-
- override fun getKnownWords(lang: String, jwt: String) {
- val url = APIUrl("$endpoint/api/v1/lesson/words/known")
-
- url.addRequestParam("lang", lang)
-
- val request = AuthenticatedStringRequest(
- url.getURL(),
- jwt,
- onSuccess = { onSucceed(it.split(",")) },
- onError = { message, status -> onFail(message, status) }
- )
-
- queue.add(request)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/PlayerViewModel.kt b/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/PlayerViewModel.kt
index d808d35a..74965109 100644
--- a/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/PlayerViewModel.kt
+++ b/app/src/main/java/cc/wordview/app/ui/activities/player/viewmodel/PlayerViewModel.kt
@@ -24,9 +24,6 @@ import androidx.compose.material.icons.filled.PlayArrow
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import cc.wordview.app.BuildConfig
-import cc.wordview.app.api.APIUrl
-import cc.wordview.app.api.getStoredJwt
-import cc.wordview.app.api.request.AuthenticatedStringRequest
import cc.wordview.app.components.media.AudioPlayer
import cc.wordview.app.components.media.AudioPlayerListener
import cc.wordview.app.database.RoomAccess
@@ -36,14 +33,10 @@ import cc.wordview.app.extractor.VideoStreamInterface
import cc.wordview.app.components.media.caption.Lyrics
import cc.wordview.app.components.media.caption.WordViewCue
import cc.wordview.app.misc.ImageCacheManager
-import cc.wordview.app.ui.dtos.PlayerToLessonCommunicator
-import cc.wordview.app.ui.activities.lesson.ReviseTimer
-import cc.wordview.app.ui.activities.lesson.viewmodel.ReviseWord
import cc.wordview.gengolex.Language
import cc.wordview.gengolex.Parser
import coil3.request.ImageRequest
import coil3.request.allowHardware
-import com.android.volley.toolbox.Volley
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
@@ -58,7 +51,6 @@ import javax.inject.Inject
@HiltViewModel
class PlayerViewModel @Inject constructor(
private val playerRepository: PlayerRepository,
- private val knownWordsRepository: KnownWordsRepository,
@ApplicationContext private val appContext: Context
) : ViewModel() {
private val _playIcon = MutableStateFlow(Icons.Filled.PlayArrow)
@@ -69,11 +61,8 @@ class PlayerViewModel @Inject constructor(
private val _playerState = MutableStateFlow(PlayerState.LOADING)
private val _finalized = MutableStateFlow(false)
private val _isBuffering = MutableStateFlow(false)
- private val _notEnoughWords = MutableStateFlow(false)
- private val _noTimeLeft = MutableStateFlow(false)
private val _errorMessage = MutableStateFlow("")
private val _statusCode = MutableStateFlow(0)
- private val _knownWords = MutableStateFlow(ArrayList())
private val _videoStream = MutableStateFlow(VideoStream())
@@ -90,8 +79,6 @@ class PlayerViewModel @Inject constructor(
val playerState = _playerState.asStateFlow()
val finalized = _finalized.asStateFlow()
val isBuffering = _isBuffering.asStateFlow()
- val notEnoughWords = _notEnoughWords.asStateFlow()
- val noTimeLeft = _noTimeLeft.asStateFlow()
val errorMessage = _errorMessage.asStateFlow()
val statusCode = _statusCode.asStateFlow()
val videoStream = _videoStream.asStateFlow()
@@ -108,41 +95,6 @@ class PlayerViewModel @Inject constructor(
setPlayerState(PlayerState.READY)
}
- fun getKnownWords(lang: Language) = viewModelScope.launch {
- val jwt = getStoredJwt(appContext)
-
- knownWordsRepository.apply {
- onFail = { message, status ->
- Timber.e("Failed to request known words \n\tmessage=$message, status=$status")
- }
-
- onSucceed = {
- for (word in it)
- _knownWords.value.add(word)
- }
-
- jwt?.let { getKnownWords(lang.tag, it) }
- }
- }
-
- fun getLessonTime() {
- val jwt = getStoredJwt(appContext) ?: return
- val queue = Volley.newRequestQueue(appContext)
- val url = APIUrl("${BuildConfig.API_BASE_URL}/api/v1/user/me/lesson_time")
-
- val request = AuthenticatedStringRequest(
- url.getURL(),
- jwt,
- onSuccess = {
- Timber.i("Time left: $it")
- ReviseTimer.timeRemaining = it.toLong()
- },
- onError = { message, status -> Timber.e("Failed to retrieve lesson time: \n\tmessage=$message, status=$status") }
- )
-
- queue.add(request)
- }
-
fun getLyrics(
id: String,
lang: Language,
@@ -204,29 +156,6 @@ class PlayerViewModel @Inject constructor(
onPlaybackEnd = {
player.value.stop()
-
- for (cue in _lyrics.value) {
- for (word in cue.words) {
- if (word.parent == "") continue
-
- val reviseWord = ReviseWord(word)
-
- reviseWord.isKnown = _knownWords.value.contains(reviseWord.tokenWord.parent)
-
- PlayerToLessonCommunicator.appendWord(reviseWord)
- }
- }
-
- val isTimerFinished = ReviseTimer.timeRemaining < 1000L
- val wordsToRevise = PlayerToLessonCommunicator.wordsToRevise.value
-
- if (isTimerFinished) {
- _noTimeLeft.update { true }
- } else if (wordsToRevise.isEmpty() || wordsToRevise.size < 3) {
- _notEnoughWords.update { true }
- } else {
- _finalized.update { true }
- }
}
}
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/statistics/Statistics.kt b/app/src/main/java/cc/wordview/app/ui/activities/statistics/Statistics.kt
deleted file mode 100644
index 66b6d1ce..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/statistics/Statistics.kt
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.statistics
-
-import androidx.activity.compose.LocalActivity
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material.icons.filled.VolumeUp
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import cc.wordview.app.R
-import cc.wordview.app.components.ui.Icon
-import cc.wordview.app.misc.AppSettings
-import cc.wordview.app.misc.ImageCacheManager
-import cc.wordview.app.components.ui.OneTimeEffect
-import cc.wordview.app.components.ui.Space
-import cc.wordview.app.ui.theme.DefaultRoundedCornerShape
-import cc.wordview.app.ui.theme.Typography
-import cc.wordview.app.ui.theme.poppinsFamily
-import cc.wordview.gengolex.Language
-import coil3.compose.AsyncImage
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Preview
-@Composable
-fun Statistics(viewModel: StatisticsViewModel = hiltViewModel()) {
- val wordsLearnedAmount by viewModel.wordsLearnedAmount.collectAsStateWithLifecycle()
- val accuracyPercentage by viewModel.accuracyPercentage.collectAsStateWithLifecycle()
- val wordsPracticedAmount by viewModel.wordsPracticedAmount.collectAsStateWithLifecycle()
- val words by viewModel.words.collectAsStateWithLifecycle()
-
- val langTag = AppSettings.language.get()
- val lang = remember { Language.byTag(langTag) }
-
- val activity = LocalActivity.current!!
-
- OneTimeEffect { viewModel.load() }
-
- Scaffold(
- topBar = {
- TopAppBar(
- colors = TopAppBarDefaults.topAppBarColors(
- containerColor = MaterialTheme.colorScheme.background,
- titleContentColor = LocalContentColor.current
- ),
- title = {
- Text(
- text = stringResource(R.string.lesson_results),
- fontFamily = poppinsFamily,
- )
- },
- navigationIcon = {
- IconButton(onClick = { activity.finish() }) {
- androidx.compose.material3.Icon(
- imageVector = Icons.Filled.ArrowBack,
- contentDescription = "Go back"
- )
- }
- }
- )
- }
- ) { innerPadding ->
- Column(
- modifier = Modifier
- .padding(innerPadding)
- .padding(horizontal = 12.dp)
- .verticalScroll(rememberScrollState())
- ) {
- Space(24.dp)
- Text(
- text = stringResource(R.string.statistics),
- fontFamily = poppinsFamily,
- fontWeight = FontWeight.Normal,
- textAlign = TextAlign.Center,
- style = Typography.titleLarge,
- )
- Space(12.dp)
- Surface(
- modifier = Modifier.fillMaxWidth(),
- shape = DefaultRoundedCornerShape,
- tonalElevation = 12.dp
- ) {
- Column(Modifier.padding(horizontal = 20.dp, vertical = 12.dp)) {
- Text(
- text = "+$wordsLearnedAmount",
- fontFamily = poppinsFamily,
- fontWeight = FontWeight.Normal,
- textAlign = TextAlign.Center,
- color = MaterialTheme.colorScheme.primary,
- style = Typography.headlineSmall,
- )
- Text(
- text = stringResource(R.string.words_learned),
- fontFamily = poppinsFamily,
- fontWeight = FontWeight.Normal,
- textAlign = TextAlign.Center,
- style = Typography.titleSmall,
- )
- Space(12.dp)
- Text(
- text = "$accuracyPercentage%",
- fontFamily = poppinsFamily,
- fontWeight = FontWeight.Normal,
- textAlign = TextAlign.Center,
- color = MaterialTheme.colorScheme.primary,
- style = Typography.headlineSmall,
- )
- Text(
- text = stringResource(R.string.accuracy),
- fontFamily = poppinsFamily,
- fontWeight = FontWeight.Normal,
- textAlign = TextAlign.Center,
- style = Typography.titleSmall,
- )
- Space(12.dp)
- Text(
- text = "$wordsPracticedAmount",
- fontFamily = poppinsFamily,
- fontWeight = FontWeight.Normal,
- textAlign = TextAlign.Center,
- color = MaterialTheme.colorScheme.primary,
- style = Typography.headlineSmall,
- )
- Text(
- text = stringResource(R.string.praticed_words),
- fontFamily = poppinsFamily,
- fontWeight = FontWeight.Normal,
- textAlign = TextAlign.Center,
- style = Typography.titleSmall,
- )
- }
- }
- Space(24.dp)
- Text(
- text = stringResource(R.string.words),
- fontFamily = poppinsFamily,
- fontWeight = FontWeight.Normal,
- textAlign = TextAlign.Center,
- style = Typography.titleLarge,
- )
- Column(Modifier.fillMaxWidth()) {
- for (word in words.distinct()) {
- Card(
- modifier = Modifier
- .testTag("result-item")
- .fillMaxWidth()
- .height(68.dp),
- colors = CardDefaults.cardColors(
- containerColor = MaterialTheme.colorScheme.surfaceContainer,
- ),
- shape = DefaultRoundedCornerShape,
- ) {
- Row(Modifier
- .fillMaxSize()
- .padding(start = 20.dp), verticalAlignment = Alignment.CenterVertically) {
- val image = ImageCacheManager.getCachedImage(word.tokenWord.parent)
- AsyncImage(
- modifier = Modifier.size(48.dp),
- model = image,
- contentDescription = null
- )
-
- Column(Modifier
- .fillMaxHeight()
- .fillMaxWidth(0.5f)
- .padding(start = 12.dp), verticalArrangement = Arrangement.Center) {
- Text(
- text = word.tokenWord.word,
- style = Typography.bodyLarge,
- )
- Text(
- text = viewModel.getTranslation(word),
- style = Typography.bodySmall,
- )
- }
-
- Column(
- modifier = Modifier
- .fillMaxHeight()
- .fillMaxWidth(1f),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.End
- ) {
- IconButton(
- modifier = Modifier.testTag("settings"),
- onClick = {
- viewModel.ttsSpeak(word.tokenWord.pronunciation ?: word.tokenWord.word, lang.locale)
- }
- ) {
- Icon(Icons.Filled.VolumeUp)
- }
- }
- }
- }
- Space(6.dp)
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/statistics/StatisticsActivity.kt b/app/src/main/java/cc/wordview/app/ui/activities/statistics/StatisticsActivity.kt
deleted file mode 100644
index 23f6f1dd..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/statistics/StatisticsActivity.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.statistics
-
-import android.os.Bundle
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import cc.wordview.app.ui.activities.WordViewActivity
-import cc.wordview.app.ui.theme.WordViewTheme
-import dagger.hilt.android.AndroidEntryPoint
-import me.zhanghai.compose.preference.ProvidePreferenceLocals
-
-@AndroidEntryPoint
-class StatisticsActivity : WordViewActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- enableEdgeToEdge()
- setContent {
- ProvidePreferenceLocals {
- WordViewTheme { Statistics() }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/activities/statistics/StatisticsViewModel.kt b/app/src/main/java/cc/wordview/app/ui/activities/statistics/StatisticsViewModel.kt
deleted file mode 100644
index 79479071..00000000
--- a/app/src/main/java/cc/wordview/app/ui/activities/statistics/StatisticsViewModel.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.activities.statistics
-
-import android.content.Context
-import android.speech.tts.TextToSpeech
-import androidx.lifecycle.ViewModel
-import cc.wordview.app.api.entity.Translation
-import cc.wordview.app.components.extensions.percentageOf
-import cc.wordview.app.ui.dtos.PlayerToLessonCommunicator
-import cc.wordview.app.ui.activities.lesson.viewmodel.ReviseWord
-import cc.wordview.app.ui.dtos.LessonToStatisticsCommunicator
-import dagger.hilt.android.lifecycle.HiltViewModel
-import dagger.hilt.android.qualifiers.ApplicationContext
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.update
-import timber.log.Timber
-import java.util.Locale
-import javax.inject.Inject
-import kotlin.math.roundToInt
-
-@HiltViewModel
-class StatisticsViewModel @Inject constructor(
- @ApplicationContext private val appContext: Context
-) : ViewModel() {
- private val _wordsLearnedAmount = MutableStateFlow(0)
- private val _accuracyPercentage = MutableStateFlow(0)
- private val _wordsPracticedAmount = MutableStateFlow(0)
- private val _translations = MutableStateFlow(ArrayList())
-
- private val _words = MutableStateFlow(arrayListOf())
-
-
- val wordsLearnedAmount = _wordsLearnedAmount.asStateFlow()
- val accuracyPercentage = _accuracyPercentage.asStateFlow()
- val wordsPracticedAmount = _wordsPracticedAmount.asStateFlow()
-
- val words = _words.asStateFlow()
-
- private var tts: TextToSpeech? = null
-
- fun load() {
- tts = PlayerToLessonCommunicator.tts
- _words.value = PlayerToLessonCommunicator.wordsToRevise.value.distinctBy { it.tokenWord.word } as ArrayList
- _wordsLearnedAmount.update { LessonToStatisticsCommunicator.wordsLearnedAmount }
- _wordsPracticedAmount.value = PlayerToLessonCommunicator.wordsToRevise.value.size
- _translations.update { LessonToStatisticsCommunicator.translations }
-
- var corrects = 0L
- var misses = 0L
-
- for (word in _words.value) {
- corrects += word.corrects
- misses += word.misses
- }
-
- val total = corrects + misses
-
- _accuracyPercentage.update { total.percentageOf(corrects).roundToInt() }
- }
-
- fun ttsSpeak(word: String, locale: Locale) {
- Timber.v("ttsSpeak: word=$word, locale=$locale")
-
- tts?.let { tts ->
- tts.language = locale
- tts.setSpeechRate(1.0f)
- tts.speak(
- word,
- TextToSpeech.QUEUE_ADD,
- null,
- null
- )
- }
- }
-
- fun getTranslation(reviseWord: ReviseWord): String {
- for (translation in _translations.value) {
- if (translation.parent == reviseWord.tokenWord.parent)
- return translation.translation
- }
-
- return reviseWord.tokenWord.parent
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/components/LessonQuitDialog.kt b/app/src/main/java/cc/wordview/app/ui/components/LessonQuitDialog.kt
deleted file mode 100644
index 8dc3eea8..00000000
--- a/app/src/main/java/cc/wordview/app/ui/components/LessonQuitDialog.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.components
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ExitToApp
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.stringResource
-import cc.wordview.app.R
-
-@Preview
-@Composable
-fun LessonQuitDialog(onDismiss: () -> Unit = {}, onConfirm: () -> Unit = {}) {
- AlertDialog(
- modifier = Modifier.testTag("lesson-quit-alert-dialog"),
- icon = {
- Icon(Icons.Filled.ExitToApp, contentDescription = null)
- },
- title = {
- Text(text = stringResource(R.string.finish_lesson))
- },
- text = {
- Text(text = stringResource(R.string.your_progress_will_be_lost_your_timer_will_be_paused_and_recovered_in_the_next_lesson))
- },
- onDismissRequest = { onDismiss() },
- confirmButton = {
- TextButton(onClick = { onConfirm() }) {
- Text(stringResource(R.string.finish))
- }
- },
- dismissButton = {
- TextButton(onClick = { onDismiss() }) {
- Text(stringResource(R.string.dismiss))
- }
- }
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/components/NoTimeLeftDialog.kt b/app/src/main/java/cc/wordview/app/ui/components/NoTimeLeftDialog.kt
deleted file mode 100644
index 6eb874d2..00000000
--- a/app/src/main/java/cc/wordview/app/ui/components/NoTimeLeftDialog.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.components
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.LockClock
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.stringResource
-import cc.wordview.app.R
-
-@Preview
-@Composable
-fun NoTimeLeftDialog(onConfirm: () -> Unit = {}) {
- AlertDialog(
- modifier = Modifier.testTag("no-time-left-dialog"),
- icon = {
- Icon(Icons.Filled.LockClock, contentDescription = null)
- },
- title = {
- Text(text = "No time left")
- },
- text = {
- Text(text = "You have used all your lesson time.")
- },
- onDismissRequest = { onConfirm() },
- confirmButton = {
- TextButton(onClick = { onConfirm() }) {
- Text(stringResource(R.string.go_back))
- }
- },
- dismissButton = {}
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/components/NotEnoughWordsDialog.kt b/app/src/main/java/cc/wordview/app/ui/components/NotEnoughWordsDialog.kt
deleted file mode 100644
index ea4f3d45..00000000
--- a/app/src/main/java/cc/wordview/app/ui/components/NotEnoughWordsDialog.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.components
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Translate
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.stringResource
-import cc.wordview.app.R
-
-@Preview
-@Composable
-fun NotEnoughWordsDialog(onConfirm: () -> Unit = {}) {
- AlertDialog(
- modifier = Modifier.testTag("not-enough-words-alert-dialog"),
- icon = {
- Icon(Icons.Filled.Translate, contentDescription = null)
- },
- title = {
- Text(text = stringResource(R.string.not_enough_words))
- },
- text = {
- Text(text = stringResource(R.string.there_were_not_enough_words_in_the_song_to_create_a_lesson))
- },
- onDismissRequest = { onConfirm() },
- confirmButton = {
- TextButton(onClick = { onConfirm() }) {
- Text(stringResource(R.string.go_back))
- }
- },
- dismissButton = {}
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/components/TranslateResultContainer.kt b/app/src/main/java/cc/wordview/app/ui/components/TranslateResultContainer.kt
deleted file mode 100644
index 5003d622..00000000
--- a/app/src/main/java/cc/wordview/app/ui/components/TranslateResultContainer.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.components
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
-import androidx.compose.foundation.layout.FlowRow
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Cancel
-import androidx.compose.material.icons.filled.CheckCircle
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import cc.wordview.app.ui.theme.DefaultRoundedCornerShape
-import cc.wordview.app.ui.theme.Typography
-import cc.wordview.app.R
-
-@OptIn(ExperimentalLayoutApi::class)
-@Composable
-fun TranslateResultContainer(correct: Boolean = true, words: List) {
- Surface(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 20.dp)
- .padding(horizontal = 5.dp),
- color = if (correct) MaterialTheme.colorScheme.primaryContainer
- else MaterialTheme.colorScheme.errorContainer,
- shape = DefaultRoundedCornerShape
- ) {
- Column {
- Row(
- Modifier
- .padding(horizontal = 20.dp)
- .padding(top = 10.dp)
- ) {
- Icon(
- imageVector = if (correct) Icons.Filled.CheckCircle else Icons.Filled.Cancel,
- contentDescription = if (correct) "Correct icon" else "Incorrect icon"
- )
- Text(
- text = if (correct) stringResource(R.string.correct_answer) else stringResource(
- R.string.wrong_answer
- ),
- modifier = Modifier.padding(start = 10.dp),
- style = Typography.titleLarge
- )
- }
- Text(
- text = if (correct) stringResource(R.string.you_answered_correctly_click_proceed_to_continue_the_lesson)
- else stringResource(R.string.you_wrongly_translated_the_phrase_the_correct_order_is),
- modifier = Modifier
- .padding(horizontal = 20.dp)
- .padding(bottom = 10.dp),
- style = Typography.bodyLarge
- )
-
- if (!correct) {
- FlowRow(
- Modifier
- .fillMaxWidth()
- .padding(horizontal = 20.dp, vertical = 10.dp)
- ) {
- words.forEachIndexed { _, word ->
- Surface(
- modifier = Modifier.padding(horizontal = 5.dp),
- shape = DefaultRoundedCornerShape
- ) {
- Text(
- text = word,
- modifier = Modifier.padding(
- horizontal = 15.dp,
- vertical = 10.dp
- ),
- style = Typography.titleLarge,
- softWrap = false
- )
- }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/dtos/LessonToStatisticsCommunicator.kt b/app/src/main/java/cc/wordview/app/ui/dtos/LessonToStatisticsCommunicator.kt
deleted file mode 100644
index a95cc175..00000000
--- a/app/src/main/java/cc/wordview/app/ui/dtos/LessonToStatisticsCommunicator.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.dtos
-
-import cc.wordview.app.api.entity.Translation
-
-object LessonToStatisticsCommunicator {
- var wordsLearnedAmount = 0
- var translations: ArrayList = arrayListOf()
-}
\ No newline at end of file
diff --git a/app/src/main/java/cc/wordview/app/ui/dtos/PlayerToLessonCommunicator.kt b/app/src/main/java/cc/wordview/app/ui/dtos/PlayerToLessonCommunicator.kt
deleted file mode 100644
index c7d96641..00000000
--- a/app/src/main/java/cc/wordview/app/ui/dtos/PlayerToLessonCommunicator.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2025 Arthur Araujo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package cc.wordview.app.ui.dtos
-
-import android.content.Context
-import android.speech.tts.TextToSpeech
-import cc.wordview.app.ui.activities.lesson.viewmodel.ReviseWord
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.update
-import timber.log.Timber
-
-/**
- * Holds the data that is prepared beforehand by the player that will be used in the lesson
- */
-object PlayerToLessonCommunicator {
- val wordsToRevise = MutableStateFlow>(arrayListOf())
- var tts: TextToSpeech? = null
-
- fun appendWord(reviseWord: ReviseWord) {
- if (wordsToRevise.value.contains(reviseWord)) return
- Timber.d("Appending '${reviseWord.tokenWord.word}' to be revised")
- wordsToRevise.update { (it + reviseWord) as ArrayList }
- }
-
- fun initTts(context: Context) {
- tts = TextToSpeech(context) {
- Timber.v("initTts: ttsStatus=$it")
- }
- }
-}
\ No newline at end of file