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