Refactor navigation and cleanup resources across the application
This commit is contained in:
@@ -126,7 +126,6 @@ dependencies {
|
|||||||
implementation(libs.androidx.room.runtime) // ADDED: Explicitly add runtime
|
implementation(libs.androidx.room.runtime) // ADDED: Explicitly add runtime
|
||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
implementation(libs.core.ktx)
|
implementation(libs.core.ktx)
|
||||||
implementation(libs.androidx.compose.foundation)
|
|
||||||
ksp(libs.room.compiler)
|
ksp(libs.room.compiler)
|
||||||
|
|
||||||
// Networking
|
// Networking
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ object TestConfig {
|
|||||||
// REPLACE with your actual API Key for the test
|
// REPLACE with your actual API Key for the test
|
||||||
const val API_KEY = "YOUR_REAL_API_KEY_HERE"
|
const val API_KEY = "YOUR_REAL_API_KEY_HERE"
|
||||||
|
|
||||||
// Set to true if you want to see full log output in Logcat
|
|
||||||
const val ENABLE_LOGGING = true
|
|
||||||
|
|
||||||
// Optional: If your ApiManager requires a specific provider (e.g., "Mistral", "OpenAI")
|
// Optional: If your ApiManager requires a specific provider (e.g., "Mistral", "OpenAI")
|
||||||
const val PROVIDER_NAME = "Mistral"
|
const val PROVIDER_NAME = "Mistral"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("unused", "HardCodedStringLiteral")
|
|
||||||
|
|
||||||
package eu.gaudian.translator.di
|
package eu.gaudian.translator.di
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ object LocalDictionaryMorphologyMapper {
|
|||||||
/**
|
/**
|
||||||
* Overload that uses parsed [DictionaryEntryData] instead of raw JSON.
|
* Overload that uses parsed [DictionaryEntryData] instead of raw JSON.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
fun parseMorphology(
|
fun parseMorphology(
|
||||||
langCode: String,
|
langCode: String,
|
||||||
pos: String?,
|
pos: String?,
|
||||||
|
|||||||
@@ -144,19 +144,6 @@ class ApiRepository(private val context: Context) {
|
|||||||
|
|
||||||
var configurationValid = true
|
var configurationValid = true
|
||||||
|
|
||||||
// (Helper function to reduce repetition)
|
|
||||||
fun checkAndFallback(current: LanguageModel?, setter: suspend (LanguageModel) -> Unit) {
|
|
||||||
val isValid = current != null && availableModels.any { it.modelId == current.modelId && it.providerKey == current.providerKey }
|
|
||||||
if (!isValid) {
|
|
||||||
val fallback = findFallbackModel(availableModels)
|
|
||||||
if (fallback != null) {
|
|
||||||
// We must use a blocking call or scope here because we can't easily pass a suspend function to a lambda
|
|
||||||
// But since we are inside a suspend function, we can just call the setter directly if we unroll the loop.
|
|
||||||
// For simplicity, I'll keep the unrolled logic below.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback checks
|
// Fallback checks
|
||||||
if (currentTrans == null || !availableModels.any { it.modelId == currentTrans.modelId && it.providerKey == currentTrans.providerKey }) {
|
if (currentTrans == null || !availableModels.any { it.modelId == currentTrans.modelId && it.providerKey == currentTrans.providerKey }) {
|
||||||
findFallbackModel(availableModels)?.let { setTranslationModel(it) } ?: run { configurationValid = false }
|
findFallbackModel(availableModels)?.let { setTranslationModel(it) } ?: run { configurationValid = false }
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class LanguageRepository(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
suspend fun wipeHistoryAndFavorites() {
|
suspend fun wipeHistoryAndFavorites() {
|
||||||
clearLanguages(LanguageListType.HISTORY)
|
clearLanguages(LanguageListType.HISTORY)
|
||||||
clearLanguages(LanguageListType.FAVORITE)
|
clearLanguages(LanguageListType.FAVORITE)
|
||||||
|
|||||||
@@ -129,25 +129,6 @@ class JsonHelper {
|
|||||||
*/
|
*/
|
||||||
class JsonParsingException(message: String, cause: Throwable? = null) : Exception(message, cause)
|
class JsonParsingException(message: String, cause: Throwable? = null) : Exception(message, cause)
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy JsonHelper class for backward compatibility.
|
|
||||||
* @deprecated Use the enhanced JsonHelper class instead
|
|
||||||
*/
|
|
||||||
@Deprecated("Use the enhanced JsonHelper class instead")
|
|
||||||
class LegacyJsonHelper {
|
|
||||||
|
|
||||||
fun cleanJson(json: String): String {
|
|
||||||
val startIndex = json.indexOf('{')
|
|
||||||
val endIndex = json.lastIndexOf('}')
|
|
||||||
|
|
||||||
if (startIndex == -1 || endIndex == -1 || startIndex >= endIndex) {
|
|
||||||
throw IllegalArgumentException("Invalid JSON format")
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.substring(startIndex, endIndex + 1).trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object JsonCleanUtil {
|
object JsonCleanUtil {
|
||||||
private val jsonParser = Json { isLenient = true; ignoreUnknownKeys = true }
|
private val jsonParser = Json { isLenient = true; ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import timber.log.Timber
|
|||||||
* "HardcodedText" lint warning for log messages, which are for
|
* "HardcodedText" lint warning for log messages, which are for
|
||||||
* development purposes only.
|
* development purposes only.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
object Log {
|
object Log {
|
||||||
|
|
||||||
@SuppressLint("HardcodedText")
|
@SuppressLint("HardcodedText")
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ enum class StatusMessageId(
|
|||||||
ERROR_FILE_PICKER_NOT_INITIALIZED(R.string.message_error_file_picker_not_initialized, MessageDisplayType.ERROR, 5),
|
ERROR_FILE_PICKER_NOT_INITIALIZED(R.string.message_error_file_picker_not_initialized, MessageDisplayType.ERROR, 5),
|
||||||
SUCCESS_CATEGORY_SAVED(R.string.message_success_category_saved, MessageDisplayType.SUCCESS, 3),
|
SUCCESS_CATEGORY_SAVED(R.string.message_success_category_saved, MessageDisplayType.SUCCESS, 3),
|
||||||
ERROR_EXCEL_NOT_SUPPORTED(R.string.message_error_excel_not_supported, MessageDisplayType.ERROR, 5),
|
ERROR_EXCEL_NOT_SUPPORTED(R.string.message_error_excel_not_supported, MessageDisplayType.ERROR, 5),
|
||||||
|
ERROR_PARSING_TABLE(R.string.error_parsing_table, MessageDisplayType.ERROR, 5),
|
||||||
|
ERROR_PARSING_TABLE_WITH_REASON(R.string.error_parsing_table_with_reason, MessageDisplayType.ERROR, 5),
|
||||||
|
ERROR_SELECT_TWO_COLUMNS(R.string.error_select_two_columns, MessageDisplayType.ERROR, 5),
|
||||||
|
ERROR_SELECT_LANGUAGES(R.string.error_select_languages, MessageDisplayType.ERROR, 5),
|
||||||
|
ERROR_NO_ROWS_TO_IMPORT(R.string.error_no_rows_to_import, MessageDisplayType.ERROR, 5),
|
||||||
|
SUCCESS_ITEMS_IMPORTED(R.string.info_imported_items_from, MessageDisplayType.SUCCESS, 3),
|
||||||
|
|
||||||
|
|
||||||
// API Key related
|
// API Key related
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ object StatusMessageService {
|
|||||||
* @deprecated Use showMessageById() instead for internationalization support.
|
* @deprecated Use showMessageById() instead for internationalization support.
|
||||||
*/
|
*/
|
||||||
@Deprecated("Use showMessageById() for internationalization support", ReplaceWith("showMessageById(messageId)"))
|
@Deprecated("Use showMessageById() for internationalization support", ReplaceWith("showMessageById(messageId)"))
|
||||||
@Suppress("unused")
|
|
||||||
fun showSimpleMessage(text: String, type: MessageDisplayType = MessageDisplayType.INFO) {
|
fun showSimpleMessage(text: String, type: MessageDisplayType = MessageDisplayType.INFO) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
_actions.emit(StatusAction.ShowMessage(text, type, 5))
|
_actions.emit(StatusAction.ShowMessage(text, type, 5))
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ class TranslationService(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun translateSentence(sentence: String): Result<TranslationHistoryItem> = withContext(Dispatchers.IO) {
|
suspend fun translateSentence(sentence: String): Result<TranslationHistoryItem> = withContext(Dispatchers.IO) {
|
||||||
val statusMessageService = StatusMessageService
|
|
||||||
val additionalInstructions = settingsRepository.customPromptTranslation.flow.first()
|
val additionalInstructions = settingsRepository.customPromptTranslation.flow.first()
|
||||||
val selectedSource = languageRepository.loadSelectedSourceLanguage().first()
|
val selectedSource = languageRepository.loadSelectedSourceLanguage().first()
|
||||||
val sourceLangName = selectedSource?.englishName ?: "Auto"
|
val sourceLangName = selectedSource?.englishName ?: "Auto"
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package eu.gaudian.translator.utils.dictionary
|
|
||||||
|
|
||||||
|
|
||||||
import eu.gaudian.translator.model.grammar.Inflection
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a language-specific inflection parser.
|
|
||||||
*/
|
|
||||||
interface InflectionParser {
|
|
||||||
fun parse(inflections: List<Inflection>): DisplayInflectionData
|
|
||||||
}
|
|
||||||
@@ -5,11 +5,6 @@ package eu.gaudian.translator.utils.dictionary
|
|||||||
* Either a simple list or a complex, grouped verb conjugation table.
|
* Either a simple list or a complex, grouped verb conjugation table.
|
||||||
*/
|
*/
|
||||||
sealed class DisplayInflectionData {
|
sealed class DisplayInflectionData {
|
||||||
data class VerbConjugation(
|
|
||||||
val gerund: String? = null,
|
|
||||||
val participle: String? = null,
|
|
||||||
val moods: List<DisplayMood>
|
|
||||||
) : DisplayInflectionData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DisplayMood(
|
data class DisplayMood(
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ private const val TRANSITION_DURATION = 300
|
|||||||
object NavigationRoutes {
|
object NavigationRoutes {
|
||||||
const val NEW_WORD = "new_word"
|
const val NEW_WORD = "new_word"
|
||||||
const val NEW_WORD_REVIEW = "new_word_review"
|
const val NEW_WORD_REVIEW = "new_word_review"
|
||||||
|
const val VOCABULARY_DETAIL = "vocabulary_detail"
|
||||||
const val START_EXERCISE = "start_exercise"
|
const val START_EXERCISE = "start_exercise"
|
||||||
const val CATEGORY_DETAIL = "category_detail"
|
const val CATEGORY_DETAIL = "category_detail"
|
||||||
const val CATEGORY_LIST = "category_list_screen"
|
const val CATEGORY_LIST = "category_list_screen"
|
||||||
const val VOCABULARY_DETAIL = "vocabulary_detail"
|
|
||||||
const val STATS_VOCABULARY_HEATMAP = "stats/vocabulary_heatmap"
|
const val STATS_VOCABULARY_HEATMAP = "stats/vocabulary_heatmap"
|
||||||
const val STATS_LANGUAGE_PROGRESS = "stats/language_progress"
|
const val STATS_LANGUAGE_PROGRESS = "stats/language_progress"
|
||||||
const val STATS_CATEGORY_DETAIL = "stats/category_detail"
|
const val STATS_CATEGORY_DETAIL = "stats/category_detail"
|
||||||
|
|||||||
@@ -2,26 +2,15 @@ package eu.gaudian.translator.view.composable
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.AccountCircle
|
|
||||||
import androidx.compose.material.icons.filled.Search
|
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
|
||||||
import androidx.compose.material3.FabPosition
|
import androidx.compose.material3.FabPosition
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
import androidx.compose.material3.ScaffoldDefaults
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.material3.contentColorFor
|
import androidx.compose.material3.contentColorFor
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.gaudian.translator.R
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppScaffold(
|
fun AppScaffold(
|
||||||
@@ -58,37 +47,3 @@ fun AppScaffold(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ParrotTopBar() {
|
|
||||||
val navyBlue = Color(0xFF1A237E) // The color from your mockup
|
|
||||||
|
|
||||||
CenterAlignedTopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = "ParrotPal",
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
color = Color.White
|
|
||||||
)
|
|
||||||
},
|
|
||||||
navigationIcon = {
|
|
||||||
// Your new parrot logo icon
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_level_parrot),
|
|
||||||
contentDescription = "Logo",
|
|
||||||
modifier = Modifier.size(32.dp),
|
|
||||||
tint = Color.Unspecified // Keeps the logo's original colors
|
|
||||||
)
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = { /* Search */ }) {
|
|
||||||
Icon(Icons.Default.Search, contentDescription = "Search", tint = Color.White)
|
|
||||||
}
|
|
||||||
IconButton(onClick = { /* Profile */ }) {
|
|
||||||
Icon(Icons.Default.AccountCircle, contentDescription = "Profile", tint = Color.White)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
|
||||||
containerColor = navyBlue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("HardCodedStringLiteral")
|
|
||||||
|
|
||||||
package eu.gaudian.translator.view.composable
|
package eu.gaudian.translator.view.composable
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
@@ -40,8 +38,8 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.gaudian.translator.ui.theme.ThemePreviews
|
|
||||||
import eu.gaudian.translator.R
|
import eu.gaudian.translator.R
|
||||||
|
import eu.gaudian.translator.ui.theme.ThemePreviews
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface that defines the required properties for any item
|
* An interface that defines the required properties for any item
|
||||||
@@ -51,15 +49,9 @@ interface TabItem {
|
|||||||
val title: String
|
val title: String
|
||||||
val icon: ImageVector
|
val icon: ImageVector
|
||||||
}
|
}
|
||||||
|
@SuppressLint("UnusedBoxWithConstraintsScope", "LocalContextResourcesRead", "DiscouragedApi",
|
||||||
/**
|
"SuspiciousIndentation"
|
||||||
* A generic, reusable tab layout composable.
|
)
|
||||||
* @param T The type of the tab item, which must implement the TabItem interface.
|
|
||||||
* @param tabs A list of all tab items to display.
|
|
||||||
* @param selectedTab The currently selected tab item.
|
|
||||||
* @param onTabSelected A lambda function to be invoked when a tab is clicked.
|
|
||||||
*/
|
|
||||||
@SuppressLint("UnusedBoxWithConstraintsScope", "LocalContextResourcesRead", "DiscouragedApi")
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T : TabItem> AppTabLayout(
|
fun <T : TabItem> AppTabLayout(
|
||||||
tabs: List<T>,
|
tabs: List<T>,
|
||||||
@@ -175,6 +167,7 @@ fun <T : TabItem> AppTabLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("HardCodedStringLiteral")
|
||||||
@ThemePreviews
|
@ThemePreviews
|
||||||
@Composable
|
@Composable
|
||||||
fun ModernTabLayoutPreview() {
|
fun ModernTabLayoutPreview() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@file:Suppress("HardCodedStringLiteral")
|
@file:Suppress("HardCodedStringLiteral", "AssignedValueIsNeverRead")
|
||||||
|
|
||||||
package eu.gaudian.translator.view.composable
|
package eu.gaudian.translator.view.composable
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -29,8 +27,6 @@ fun CategorySelectionDialog(
|
|||||||
val activity = LocalContext.current.findActivity()
|
val activity = LocalContext.current.findActivity()
|
||||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
|
|
||||||
val categories by categoryViewModel.categories.collectAsState(initial = emptyList())
|
|
||||||
|
|
||||||
AppDialog(
|
AppDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = {
|
title = {
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
package eu.gaudian.translator.view.dialogs
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
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.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|
||||||
import eu.gaudian.translator.R
|
|
||||||
import eu.gaudian.translator.model.Language
|
|
||||||
import eu.gaudian.translator.model.VocabularyCategory
|
|
||||||
import eu.gaudian.translator.model.VocabularyStage
|
|
||||||
import eu.gaudian.translator.utils.findActivity
|
|
||||||
import eu.gaudian.translator.view.composable.AppDialog
|
|
||||||
import eu.gaudian.translator.view.composable.MultipleLanguageDropdown
|
|
||||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
|
||||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
|
||||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StartExerciseDialog(
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
onConfirm: (
|
|
||||||
categories: List<VocabularyCategory>,
|
|
||||||
stages: List<VocabularyStage>,
|
|
||||||
languageIds: List<Int>
|
|
||||||
) -> Unit
|
|
||||||
) {
|
|
||||||
val activity = LocalContext.current.findActivity()
|
|
||||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val categories by categoryViewModel.categories.collectAsState(initial = emptyList())
|
|
||||||
|
|
||||||
var lids by remember { mutableStateOf<List<Int>>(emptyList()) }
|
|
||||||
var languages by remember { mutableStateOf<List<Language>>(emptyList()) }
|
|
||||||
// Map displayed Language to its DB id (lid) using position mapping from load
|
|
||||||
var languageIdMap by remember { mutableStateOf<Map<Language, Int>>(emptyMap()) }
|
|
||||||
var selectedLanguages by remember { mutableStateOf<List<Language>>(emptyList()) }
|
|
||||||
var selectedStages by remember { mutableStateOf<List<VocabularyStage>>(emptyList()) }
|
|
||||||
var selectedCategories by remember { mutableStateOf<List<VocabularyCategory>>(emptyList()) }
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
lids = vocabularyViewModel.getAllLanguagesIdsPresent().filterNotNull().toList()
|
|
||||||
languages = lids.map { lid ->
|
|
||||||
languageViewModel.getLanguageById(lid)
|
|
||||||
}
|
|
||||||
// build reverse map
|
|
||||||
languageIdMap = languages.mapIndexed { index, lang -> lang to lids.getOrNull(index) }.filter { it.second != null }.associate { it.first to it.second!! }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppDialog(onDismissRequest = onDismiss, title = { Text(stringResource(R.string.label_start_exercise)) }) {
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(24.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
MultipleLanguageDropdown(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
languageViewModel = languageViewModel,
|
|
||||||
onLanguagesSelected = { langs ->
|
|
||||||
selectedLanguages = langs
|
|
||||||
},
|
|
||||||
languages
|
|
||||||
)
|
|
||||||
CategoryDropdown(
|
|
||||||
onCategorySelected = { cats ->
|
|
||||||
selectedCategories = cats.filterIsInstance<VocabularyCategory>()
|
|
||||||
},
|
|
||||||
multipleSelectable = true,
|
|
||||||
onlyLists = false, // Show both filters and lists
|
|
||||||
addCategory = false,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
)
|
|
||||||
VocabularyStageDropDown(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
preselectedStages = selectedStages,
|
|
||||||
onStageSelected = { stages ->
|
|
||||||
@Suppress("FilterIsInstanceResultIsAlwaysEmpty")
|
|
||||||
selectedStages = stages.filterIsInstance<VocabularyStage>()
|
|
||||||
},
|
|
||||||
multipleSelectable = true
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 16.dp),
|
|
||||||
horizontalArrangement = Arrangement.End
|
|
||||||
) {
|
|
||||||
TextButton(
|
|
||||||
onClick = onDismiss,
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.label_cancel))
|
|
||||||
}
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
run {
|
|
||||||
val ids = selectedLanguages.mapNotNull { languageIdMap[it] }
|
|
||||||
onConfirm(selectedCategories, selectedStages, ids)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.label_start_exercise))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,6 @@ import eu.gaudian.translator.view.composable.AppCheckbox
|
|||||||
import eu.gaudian.translator.view.composable.AppScaffold
|
import eu.gaudian.translator.view.composable.AppScaffold
|
||||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||||
import eu.gaudian.translator.view.hints.HintDefinition
|
import eu.gaudian.translator.view.hints.HintDefinition
|
||||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
|
||||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -45,10 +44,8 @@ fun VocabularyReviewScreen(
|
|||||||
) {
|
) {
|
||||||
val activity = LocalContext.current.findActivity()
|
val activity = LocalContext.current.findActivity()
|
||||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(activity)
|
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(activity)
|
||||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
|
|
||||||
val generatedItems: List<VocabularyItem> by vocabularyViewModel.generatedVocabularyItems.collectAsState()
|
val generatedItems: List<VocabularyItem> by vocabularyViewModel.generatedVocabularyItems.collectAsState()
|
||||||
val categories by categoryViewModel.categories.collectAsState(initial = emptyList())
|
|
||||||
|
|
||||||
val selectedItems = remember { mutableStateListOf<VocabularyItem>() }
|
val selectedItems = remember { mutableStateListOf<VocabularyItem>() }
|
||||||
val duplicates = remember { mutableStateListOf<Boolean>() }
|
val duplicates = remember { mutableStateListOf<Boolean>() }
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ fun DefinitionPart(part: EntryPart) {
|
|||||||
// Fallback for JsonObject or other top-level types
|
// Fallback for JsonObject or other top-level types
|
||||||
else -> contentElement.toString()
|
else -> contentElement.toString()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
// Ultimate fallback if something else goes wrong during parsing
|
// Ultimate fallback if something else goes wrong during parsing
|
||||||
part.content.toString()
|
part.content.toString()
|
||||||
}
|
}
|
||||||
@@ -466,12 +466,6 @@ fun DefinitionPartPreview() {
|
|||||||
DefinitionPart(part = mockPart)
|
DefinitionPart(part = mockPart)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data classes for the refactored components
|
|
||||||
data class EntryData(
|
|
||||||
val entry: DictionaryEntry,
|
|
||||||
val language: Language?
|
|
||||||
)
|
|
||||||
|
|
||||||
data class BreadcrumbItem(
|
data class BreadcrumbItem(
|
||||||
val word: String,
|
val word: String,
|
||||||
val entryId: Int
|
val entryId: Int
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("SameParameterValue")
|
||||||
|
|
||||||
package eu.gaudian.translator.view.dictionary
|
package eu.gaudian.translator.view.dictionary
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import androidx.compose.material3.rememberModalBottomSheetState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -58,7 +59,6 @@ import eu.gaudian.translator.model.Language
|
|||||||
import eu.gaudian.translator.model.TagCategory
|
import eu.gaudian.translator.model.TagCategory
|
||||||
import eu.gaudian.translator.model.VocabularyCategory
|
import eu.gaudian.translator.model.VocabularyCategory
|
||||||
import eu.gaudian.translator.model.VocabularyStage
|
import eu.gaudian.translator.model.VocabularyStage
|
||||||
import eu.gaudian.translator.utils.Log
|
|
||||||
import eu.gaudian.translator.utils.findActivity
|
import eu.gaudian.translator.utils.findActivity
|
||||||
import eu.gaudian.translator.view.composable.AppButton
|
import eu.gaudian.translator.view.composable.AppButton
|
||||||
import eu.gaudian.translator.view.composable.AppIcons
|
import eu.gaudian.translator.view.composable.AppIcons
|
||||||
@@ -125,10 +125,9 @@ fun StartExerciseScreen(
|
|||||||
ids
|
ids
|
||||||
}
|
}
|
||||||
|
|
||||||
var amount by remember { mutableStateOf(0) }
|
var amount by remember { mutableIntStateOf(0) }
|
||||||
androidx.compose.runtime.LaunchedEffect(totalItemCount) {
|
androidx.compose.runtime.LaunchedEffect(totalItemCount) {
|
||||||
amount = totalItemCount
|
amount = totalItemCount
|
||||||
Log.d("StartExercise", "Items to show updated: total=$totalItemCount, amount=$amount")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val updateConfig: (eu.gaudian.translator.viewmodel.ExerciseConfig) -> Unit = { config ->
|
val updateConfig: (eu.gaudian.translator.viewmodel.ExerciseConfig) -> Unit = { config ->
|
||||||
@@ -251,14 +250,12 @@ fun StartExerciseScreen(
|
|||||||
enabled = totalItemCount > 0 && amount > 0,
|
enabled = totalItemCount > 0 && amount > 0,
|
||||||
amount = amount,
|
amount = amount,
|
||||||
onStart = {
|
onStart = {
|
||||||
Log.d("StartExercise", "Start pressed. shuffleCards=${exerciseConfig.shuffleCards}, selectedAmount=$amount, items=${itemsToShow.size}, origin=${selectedOriginLanguage?.nameResId}, target=${selectedTargetLanguage?.nameResId}, pairs=${selectedPairsIds.size}, categories=${selectedCategoryIds.size}, stages=${selectedStages.size}")
|
|
||||||
val finalItems = if (exerciseConfig.shuffleCards) {
|
val finalItems = if (exerciseConfig.shuffleCards) {
|
||||||
itemsToShow.shuffled().take(amount)
|
itemsToShow.shuffled().take(amount)
|
||||||
} else {
|
} else {
|
||||||
itemsToShow.take(amount)
|
itemsToShow.take(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("StartExercise", "Final items prepared: count=${finalItems.size}")
|
|
||||||
|
|
||||||
exerciseViewModel.startExerciseWithConfig(
|
exerciseViewModel.startExerciseWithConfig(
|
||||||
finalItems,
|
finalItems,
|
||||||
@@ -270,7 +267,7 @@ fun StartExerciseScreen(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Log.d("StartExercise", "Navigating to vocabulary_exercise/false")
|
@Suppress("HardCodedStringLiteral")
|
||||||
navController.navigate("vocabulary_exercise/false")
|
navController.navigate("vocabulary_exercise/false")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -307,7 +304,7 @@ fun TopBarSection(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ArrowBackIosNew,
|
imageVector = Icons.Default.ArrowBackIosNew,
|
||||||
contentDescription = "Back",
|
contentDescription = stringResource(R.string.cd_back),
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
@@ -329,7 +326,7 @@ fun TopBarSection(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Settings,
|
imageVector = Icons.Default.Settings,
|
||||||
contentDescription = "Settings",
|
contentDescription = stringResource(R.string.cd_settings),
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
@@ -349,6 +346,7 @@ fun TopBarSection(
|
|||||||
onDismiss = {
|
onDismiss = {
|
||||||
scope.launch { sheetState.hide() }.invokeOnCompletion {
|
scope.launch { sheetState.hide() }.invokeOnCompletion {
|
||||||
if (!sheetState.isVisible) {
|
if (!sheetState.isVisible) {
|
||||||
|
@Suppress("AssignedValueIsNeverRead")
|
||||||
showSettings = false
|
showSettings = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,7 +358,9 @@ fun TopBarSection(
|
|||||||
@Composable
|
@Composable
|
||||||
fun SectionHeader(title: String, actionText: String? = null, onActionClick: () -> Unit = {}) {
|
fun SectionHeader(title: String, actionText: String? = null, onActionClick: () -> Unit = {}) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -398,7 +398,6 @@ fun LanguagePairSection(
|
|||||||
val activity = androidx.compose.ui.platform.LocalContext.current.findActivity()
|
val activity = androidx.compose.ui.platform.LocalContext.current.findActivity()
|
||||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
val languagesPresent by vocabularyViewModel.languagesPresent.collectAsState(initial = emptySet())
|
|
||||||
val allLanguages by languageViewModel.allLanguages.collectAsState(initial = emptyList())
|
val allLanguages by languageViewModel.allLanguages.collectAsState(initial = emptyList())
|
||||||
|
|
||||||
val availableLanguages = remember(availableLanguageIds, allLanguages) {
|
val availableLanguages = remember(availableLanguageIds, allLanguages) {
|
||||||
@@ -549,8 +548,16 @@ fun LanguageChip(text: String, isSelected: Boolean, modifier: Modifier = Modifie
|
|||||||
) {
|
) {
|
||||||
// Dummy overlapping flags
|
// Dummy overlapping flags
|
||||||
Box(modifier = Modifier.width(32.dp)) {
|
Box(modifier = Modifier.width(32.dp)) {
|
||||||
Box(modifier = Modifier.size(20.dp).clip(CircleShape).background(Color.Red).align(Alignment.CenterStart))
|
Box(modifier = Modifier
|
||||||
Box(modifier = Modifier.size(20.dp).clip(CircleShape).background(Color.Blue).align(Alignment.CenterEnd))
|
.size(20.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color.Red)
|
||||||
|
.align(Alignment.CenterStart))
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color.Blue)
|
||||||
|
.align(Alignment.CenterEnd))
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(text = text, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium)
|
Text(text = text, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium)
|
||||||
@@ -690,7 +697,9 @@ fun NumberOfCardsSection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ enum class HintDefinition(
|
|||||||
@Composable
|
@Composable
|
||||||
fun hint(definition: HintDefinition): Hint = definition.hint()
|
fun hint(definition: HintDefinition): Hint = definition.hint()
|
||||||
|
|
||||||
@Composable fun HintContent(definition: HintDefinition) = definition.Render()
|
|
||||||
@Composable fun HintScreen(navController: NavController, definition: HintDefinition) = HintScreen(
|
@Composable fun HintScreen(navController: NavController, definition: HintDefinition) = HintScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
title = stringResource(definition.titleRes),
|
title = stringResource(definition.titleRes),
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("HardCodedStringLiteral")
|
|
||||||
|
|
||||||
package eu.gaudian.translator.view.hints
|
package eu.gaudian.translator.view.hints
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ object MarkdownHintLoader {
|
|||||||
append(language.lowercase())
|
append(language.lowercase())
|
||||||
}
|
}
|
||||||
if (country.isNotEmpty()) {
|
if (country.isNotEmpty()) {
|
||||||
|
@Suppress("HardCodedStringLiteral")
|
||||||
append("-r")
|
append("-r")
|
||||||
append(country.uppercase())
|
append(country.uppercase())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ fun LanguageOptionsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showAddLanguageDialog) {
|
if (showAddLanguageDialog) {
|
||||||
|
@Suppress("KotlinConstantConditions")
|
||||||
AddCustomLanguageDialog(
|
AddCustomLanguageDialog(
|
||||||
showDialog = showAddLanguageDialog,
|
showDialog = showAddLanguageDialog,
|
||||||
onDismiss = { showAddLanguageDialog = false },
|
onDismiss = { showAddLanguageDialog = false },
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ fun LayoutOptionsScreen(navController: NavController) {
|
|||||||
val selectedFontName by settingsViewModel.fontPreference.collectAsStateWithLifecycle()
|
val selectedFontName by settingsViewModel.fontPreference.collectAsStateWithLifecycle()
|
||||||
val showBottomNavLabels by settingsViewModel.showBottomNavLabels.collectAsStateWithLifecycle()
|
val showBottomNavLabels by settingsViewModel.showBottomNavLabels.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val cdBack = stringResource(R.string.cd_back)
|
|
||||||
AppScaffold(
|
AppScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopAppBar(
|
AppTopAppBar(
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ fun VocabularyProgressOptionsScreen(
|
|||||||
AppScaffold(
|
AppScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopAppBar(
|
AppTopAppBar(
|
||||||
title = stringResource(R.string.vocabulary_settings),
|
title = stringResource(R.string.label_vocabulary_settings),
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
hintContent = HintDefinition.VOCABULARY_PROGRESS.hint()
|
hintContent = HintDefinition.VOCABULARY_PROGRESS.hint()
|
||||||
)
|
)
|
||||||
@@ -101,7 +101,7 @@ fun VocabularyProgressOptionsScreen(
|
|||||||
|
|
||||||
@Suppress("USELESS_ELVIS", "HardCodedStringLiteral")
|
@Suppress("USELESS_ELVIS", "HardCodedStringLiteral")
|
||||||
SettingsSlider(
|
SettingsSlider(
|
||||||
label = stringResource(R.string.target_correct_answers_per_day),
|
label = stringResource(R.string.label_target_correct_answers_per_day),
|
||||||
value = dailyGoal ?: 10,
|
value = dailyGoal ?: 10,
|
||||||
onValueChange = { settingsViewModel.setDailyGoal(it) },
|
onValueChange = { settingsViewModel.setDailyGoal(it) },
|
||||||
valueRange = 10f..100f,
|
valueRange = 10f..100f,
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import androidx.navigation.compose.rememberNavController
|
|||||||
import eu.gaudian.translator.R
|
import eu.gaudian.translator.R
|
||||||
import eu.gaudian.translator.model.VocabularyStage
|
import eu.gaudian.translator.model.VocabularyStage
|
||||||
import eu.gaudian.translator.model.WidgetType
|
import eu.gaudian.translator.model.WidgetType
|
||||||
import eu.gaudian.translator.utils.Log
|
|
||||||
import eu.gaudian.translator.utils.findActivity
|
import eu.gaudian.translator.utils.findActivity
|
||||||
import eu.gaudian.translator.view.composable.AppCard
|
import eu.gaudian.translator.view.composable.AppCard
|
||||||
import eu.gaudian.translator.view.composable.AppIcons
|
import eu.gaudian.translator.view.composable.AppIcons
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ fun NewWordReviewScreen(
|
|||||||
onConfirm = {
|
onConfirm = {
|
||||||
val selectedCategoryIds = selectedCategories.filterNotNull().map { it.id }
|
val selectedCategoryIds = selectedCategories.filterNotNull().map { it.id }
|
||||||
vocabularyViewModel.addVocabularyItems(selectedItems.toList(), selectedCategoryIds)
|
vocabularyViewModel.addVocabularyItems(selectedItems.toList(), selectedCategoryIds)
|
||||||
|
@Suppress("HardCodedStringLiteral")
|
||||||
navController.popBackStack("new_word", inclusive = false)
|
navController.popBackStack("new_word", inclusive = false)
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.AutoAwesome
|
import androidx.compose.material.icons.filled.AutoAwesome
|
||||||
import androidx.compose.material.icons.filled.DriveFolderUpload
|
import androidx.compose.material.icons.filled.DriveFolderUpload
|
||||||
import androidx.compose.material.icons.filled.EditNote
|
import androidx.compose.material.icons.filled.EditNote
|
||||||
import androidx.compose.material.icons.filled.LibraryBooks
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
@@ -62,12 +61,15 @@ import eu.gaudian.translator.model.VocabularyItem
|
|||||||
import eu.gaudian.translator.utils.StatusMessageId
|
import eu.gaudian.translator.utils.StatusMessageId
|
||||||
import eu.gaudian.translator.utils.StatusMessageService
|
import eu.gaudian.translator.utils.StatusMessageService
|
||||||
import eu.gaudian.translator.utils.findActivity
|
import eu.gaudian.translator.utils.findActivity
|
||||||
|
import eu.gaudian.translator.view.NavigationRoutes
|
||||||
import eu.gaudian.translator.view.composable.AppButton
|
import eu.gaudian.translator.view.composable.AppButton
|
||||||
import eu.gaudian.translator.view.composable.AppCard
|
import eu.gaudian.translator.view.composable.AppCard
|
||||||
|
import eu.gaudian.translator.view.composable.AppIcons
|
||||||
import eu.gaudian.translator.view.composable.AppOutlinedButton
|
import eu.gaudian.translator.view.composable.AppOutlinedButton
|
||||||
import eu.gaudian.translator.view.composable.AppSlider
|
import eu.gaudian.translator.view.composable.AppSlider
|
||||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||||
import eu.gaudian.translator.view.composable.InspiringSearchField
|
import eu.gaudian.translator.view.composable.InspiringSearchField
|
||||||
|
import eu.gaudian.translator.view.composable.Screen
|
||||||
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
|
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
|
||||||
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
|
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
|
||||||
import eu.gaudian.translator.view.hints.HintDefinition
|
import eu.gaudian.translator.view.hints.HintDefinition
|
||||||
@@ -96,7 +98,7 @@ fun NewWordScreen(
|
|||||||
LaunchedEffect(isGenerating, generatedItems, navigateToReview) {
|
LaunchedEffect(isGenerating, generatedItems, navigateToReview) {
|
||||||
if (navigateToReview && !isGenerating) {
|
if (navigateToReview && !isGenerating) {
|
||||||
if (generatedItems.isNotEmpty()) {
|
if (generatedItems.isNotEmpty()) {
|
||||||
navController.navigate("new_word_review")
|
navController.navigate(NavigationRoutes.NEW_WORD_REVIEW)
|
||||||
}
|
}
|
||||||
navigateToReview = false
|
navigateToReview = false
|
||||||
}
|
}
|
||||||
@@ -112,7 +114,6 @@ fun NewWordScreen(
|
|||||||
var skipHeader by remember { mutableStateOf(true) }
|
var skipHeader by remember { mutableStateOf(true) }
|
||||||
var selectedLangFirst by remember { mutableStateOf<Language?>(null) }
|
var selectedLangFirst by remember { mutableStateOf<Language?>(null) }
|
||||||
var selectedLangSecond by remember { mutableStateOf<Language?>(null) }
|
var selectedLangSecond by remember { mutableStateOf<Language?>(null) }
|
||||||
var parseError by remember { mutableStateOf<String?>(null) }
|
|
||||||
|
|
||||||
val recentlyAdded = remember(recentItems) {
|
val recentlyAdded = remember(recentItems) {
|
||||||
recentItems.sortedByDescending { it.id }.take(4)
|
recentItems.sortedByDescending { it.id }.take(4)
|
||||||
@@ -172,8 +173,6 @@ fun NewWordScreen(
|
|||||||
}.filter { r -> r.any { it.isNotBlank() } }
|
}.filter { r -> r.any { it.isNotBlank() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
val errorParsingTable = stringResource(R.string.error_parsing_table)
|
|
||||||
val errorParsingTableWithReason = stringResource(R.string.error_parsing_table_with_reason)
|
|
||||||
val importTableLauncher = rememberLauncherForActivityResult(
|
val importTableLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.OpenDocument(),
|
contract = ActivityResultContracts.OpenDocument(),
|
||||||
onResult = { uri ->
|
onResult = { uri ->
|
||||||
@@ -197,34 +196,32 @@ fun NewWordScreen(
|
|||||||
selectedColFirst = 0
|
selectedColFirst = 0
|
||||||
selectedColSecond = 1.coerceAtMost(rows.first().size - 1)
|
selectedColSecond = 1.coerceAtMost(rows.first().size - 1)
|
||||||
showTableImportDialog.value = true
|
showTableImportDialog.value = true
|
||||||
parseError = null
|
|
||||||
} else {
|
} else {
|
||||||
parseError = errorParsingTable
|
statusMessageService.showErrorById(StatusMessageId.ERROR_PARSING_TABLE)
|
||||||
statusMessageService.showErrorMessage(parseError!!)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
parseError = e.message
|
statusMessageService.showErrorById(StatusMessageId.ERROR_PARSING_TABLE_WITH_REASON)
|
||||||
statusMessageService.showErrorMessage(
|
|
||||||
(errorParsingTableWithReason + " " + e.message)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier.fillMaxSize().padding(16.dp),
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
contentAlignment = Alignment.TopCenter
|
contentAlignment = Alignment.TopCenter
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(max = 700.dp) // Perfect scaling for tablets/foldables
|
.widthIn(max = 700.dp) // Perfect scaling for tablets/foldables
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState()).padding(0.dp)
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(0.dp)
|
||||||
) {
|
) {
|
||||||
AppTopAppBar(
|
AppTopAppBar(
|
||||||
title = "New Words",
|
title = stringResource(R.string.label_new_words),
|
||||||
onNavigateBack = { navController.popBackStack() }
|
onNavigateBack = { navController.popBackStack() }
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -282,12 +279,12 @@ fun NewWordScreen(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Recently Added",
|
text = stringResource(R.string.label_recently_added),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
TextButton(onClick = { navController.navigate("library") }) {
|
TextButton(onClick = { navController.navigate(Screen.Library.route) }) {
|
||||||
Text("View All")
|
Text(stringResource(R.string.label_view_all))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
@@ -297,7 +294,10 @@ fun NewWordScreen(
|
|||||||
item = item,
|
item = item,
|
||||||
allLanguages = allLanguages,
|
allLanguages = allLanguages,
|
||||||
isSelected = false,
|
isSelected = false,
|
||||||
onItemClick = { navController.navigate("vocabulary_detail/${item.id}") },
|
onItemClick = {
|
||||||
|
@Suppress("HardCodedStringLiteral")
|
||||||
|
navController.navigate("${NavigationRoutes.VOCABULARY_DETAIL}/${item.id}")
|
||||||
|
},
|
||||||
onItemLongClick = {},
|
onItemLongClick = {},
|
||||||
onDeleteClick = {}
|
onDeleteClick = {}
|
||||||
)
|
)
|
||||||
@@ -388,19 +388,15 @@ fun NewWordScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
val errorSelectTwoColumns = stringResource(R.string.error_select_two_columns)
|
|
||||||
val errorSelectLanguages = stringResource(R.string.error_select_languages)
|
|
||||||
val errorNoRowsToImport = stringResource(R.string.error_no_rows_to_import)
|
|
||||||
val infoImportedItemsFrom = stringResource(R.string.info_imported_items_from)
|
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
if (selectedColFirst == selectedColSecond) {
|
if (selectedColFirst == selectedColSecond) {
|
||||||
statusMessageService.showErrorMessage(errorSelectTwoColumns)
|
statusMessageService.showErrorById(StatusMessageId.ERROR_SELECT_TWO_COLUMNS)
|
||||||
return@TextButton
|
return@TextButton
|
||||||
}
|
}
|
||||||
val langA = selectedLangFirst
|
val langA = selectedLangFirst
|
||||||
val langB = selectedLangSecond
|
val langB = selectedLangSecond
|
||||||
if (langA == null || langB == null) {
|
if (langA == null || langB == null) {
|
||||||
statusMessageService.showErrorMessage(errorSelectLanguages)
|
statusMessageService.showErrorById(StatusMessageId.ERROR_SELECT_LANGUAGES)
|
||||||
return@TextButton
|
return@TextButton
|
||||||
}
|
}
|
||||||
val startIdx = if (skipHeader) 1 else 0
|
val startIdx = if (skipHeader) 1 else 0
|
||||||
@@ -416,11 +412,11 @@ fun NewWordScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
statusMessageService.showErrorMessage(errorNoRowsToImport)
|
statusMessageService.showErrorById(StatusMessageId.ERROR_NO_ROWS_TO_IMPORT)
|
||||||
return@TextButton
|
return@TextButton
|
||||||
}
|
}
|
||||||
vocabularyViewModel.addVocabularyItems(items)
|
vocabularyViewModel.addVocabularyItems(items)
|
||||||
statusMessageService.showSuccessMessage(infoImportedItemsFrom + " " + items.size)
|
statusMessageService.showSuccessById(StatusMessageId.SUCCESS_ITEMS_IMPORTED)
|
||||||
showTableImportDialog.value = false
|
showTableImportDialog.value = false
|
||||||
}) { Text(stringResource(R.string.label_import)) }
|
}) { Text(stringResource(R.string.label_import)) }
|
||||||
},
|
},
|
||||||
@@ -451,7 +447,7 @@ fun AIGeneratorCard(
|
|||||||
val hints = stringArrayResource(R.array.vocabulary_hints)
|
val hints = stringArrayResource(R.array.vocabulary_hints)
|
||||||
AppCard(
|
AppCard(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
title = "AI Generator",
|
title = stringResource(R.string.label_ai_generator),
|
||||||
icon = icon,
|
icon = icon,
|
||||||
hintContent = HintDefinition.VOCABULARY_GENERATE_AI.hint(),
|
hintContent = HintDefinition.VOCABULARY_GENERATE_AI.hint(),
|
||||||
) {
|
) {
|
||||||
@@ -561,12 +557,6 @@ fun AddManuallyCard(
|
|||||||
val selectedSourceLanguage by languageViewModel.selectedSourceLanguage.collectAsState()
|
val selectedSourceLanguage by languageViewModel.selectedSourceLanguage.collectAsState()
|
||||||
val selectedTargetLanguage by languageViewModel.selectedTargetLanguage.collectAsState()
|
val selectedTargetLanguage by languageViewModel.selectedTargetLanguage.collectAsState()
|
||||||
|
|
||||||
val languageLabel = when {
|
|
||||||
selectedSourceLanguage != null && selectedTargetLanguage != null ->
|
|
||||||
"${selectedSourceLanguage?.name} → ${selectedTargetLanguage?.name}"
|
|
||||||
else -> stringResource(R.string.text_select_languages)
|
|
||||||
}
|
|
||||||
|
|
||||||
val canAdd = wordText.isNotBlank() && translationText.isNotBlank() &&
|
val canAdd = wordText.isNotBlank() && translationText.isNotBlank() &&
|
||||||
selectedSourceLanguage != null && selectedTargetLanguage != null
|
selectedSourceLanguage != null && selectedTargetLanguage != null
|
||||||
|
|
||||||
@@ -709,9 +699,11 @@ fun BottomActionCardsRow(
|
|||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
// Explore Packs Card
|
//TODO Explore Packs Card
|
||||||
AppCard(
|
AppCard(
|
||||||
modifier = Modifier.weight(1f).height(120.dp),
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(120.dp),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -728,12 +720,13 @@ fun BottomActionCardsRow(
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.LibraryBooks,
|
imageVector = AppIcons.Vocabulary,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.primary
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
@Suppress("HardCodedStringLiteral")
|
||||||
Text(
|
Text(
|
||||||
text = "Explore Packs",
|
text = "Explore Packs",
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
@@ -741,6 +734,7 @@ fun BottomActionCardsRow(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
@Suppress("HardCodedStringLiteral")
|
||||||
Text(
|
Text(
|
||||||
text = "Coming soon",
|
text = "Coming soon",
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
@@ -751,7 +745,9 @@ fun BottomActionCardsRow(
|
|||||||
|
|
||||||
// Import CSV Card
|
// Import CSV Card
|
||||||
AppCard(
|
AppCard(
|
||||||
modifier = Modifier.weight(1f).height(120.dp),
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(120.dp),
|
||||||
onClick = onImportCsvClick
|
onClick = onImportCsvClick
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -774,7 +770,7 @@ fun BottomActionCardsRow(
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Import CSV",
|
text = stringResource(R.string.label_import_csv),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ fun NoGrammarItemsScreen(
|
|||||||
|
|
||||||
var showFetchGrammarDialog by remember { mutableStateOf(false) }
|
var showFetchGrammarDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@Suppress("UnusedVariable", "unused", "HardCodedStringLiteral") val onClose = { navController.popBackStack() }
|
@Suppress("UnusedVariable") val onClose = { navController.popBackStack() }
|
||||||
|
|
||||||
if (itemsWithoutGrammar.isEmpty() && !isGenerating) {
|
if (itemsWithoutGrammar.isEmpty() && !isGenerating) {
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
@@ -1,467 +0,0 @@
|
|||||||
package eu.gaudian.translator.view.vocabulary
|
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
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.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.CardDefaults
|
|
||||||
import androidx.compose.material3.DividerDefaults
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|
||||||
import eu.gaudian.translator.R
|
|
||||||
import eu.gaudian.translator.model.CardSet
|
|
||||||
import eu.gaudian.translator.model.Language
|
|
||||||
import eu.gaudian.translator.model.VocabularyItem
|
|
||||||
import eu.gaudian.translator.utils.findActivity
|
|
||||||
import eu.gaudian.translator.view.composable.AppButton
|
|
||||||
import eu.gaudian.translator.view.composable.AppIcons
|
|
||||||
import eu.gaudian.translator.view.composable.AppOutlinedButton
|
|
||||||
import eu.gaudian.translator.view.composable.AppScaffold
|
|
||||||
import eu.gaudian.translator.view.composable.AppSlider
|
|
||||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
|
||||||
import eu.gaudian.translator.view.composable.OptionItemSwitch
|
|
||||||
import eu.gaudian.translator.view.composable.SingleLanguageDropDown
|
|
||||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
|
||||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StartScreen(
|
|
||||||
cardSet: CardSet?,
|
|
||||||
onStartClicked: (List<VocabularyItem>) -> Unit,
|
|
||||||
onClose: () -> Unit,
|
|
||||||
shuffleCards: Boolean,
|
|
||||||
onShuffleCardsChanged: (Boolean) -> Unit,
|
|
||||||
shuffleLanguages: Boolean,
|
|
||||||
onShuffleLanguagesChanged: (Boolean) -> Unit,
|
|
||||||
trainingMode: Boolean,
|
|
||||||
onTrainingModeChanged: (Boolean) -> Unit,
|
|
||||||
dueTodayOnly: Boolean,
|
|
||||||
onDueTodayOnlyChanged: (Boolean) -> Unit,
|
|
||||||
selectedExerciseTypes: Set<VocabularyExerciseType>,
|
|
||||||
onExerciseTypeSelected: (VocabularyExerciseType) -> Unit,
|
|
||||||
hideTodayOnlySwitch: Boolean = false,
|
|
||||||
selectedOriginLanguage: Language?,
|
|
||||||
onOriginLanguageChanged: (Language?) -> Unit,
|
|
||||||
selectedTargetLanguage: Language?,
|
|
||||||
onTargetLanguageChanged: (Language?) -> Unit,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val activity = LocalContext.current.findActivity()
|
|
||||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
val dueTodayItems by vocabularyViewModel.dueTodayItems.collectAsState(initial = emptyList())
|
|
||||||
val allItems = cardSet?.cards ?: emptyList()
|
|
||||||
|
|
||||||
var amount by remember(allItems) { mutableIntStateOf(allItems.size) }
|
|
||||||
|
|
||||||
val itemsToShow = if (dueTodayOnly) {
|
|
||||||
allItems.filter { card -> dueTodayItems.any { it.id == card.id } }
|
|
||||||
} else {
|
|
||||||
allItems
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount > itemsToShow.size) {
|
|
||||||
amount = itemsToShow.size
|
|
||||||
}
|
|
||||||
|
|
||||||
StartScreenContent(
|
|
||||||
vocabularyItemsCount = itemsToShow.size,
|
|
||||||
shuffleCards = shuffleCards,
|
|
||||||
onShuffleCardsChanged = onShuffleCardsChanged,
|
|
||||||
shuffleLanguages = shuffleLanguages,
|
|
||||||
onShuffleLanguagesChanged = onShuffleLanguagesChanged,
|
|
||||||
trainingMode = trainingMode,
|
|
||||||
onTrainingModeChanged = onTrainingModeChanged,
|
|
||||||
dueTodayOnly = dueTodayOnly,
|
|
||||||
onDueTodayOnlyChanged = onDueTodayOnlyChanged,
|
|
||||||
amount = amount,
|
|
||||||
onAmountChanged = {
|
|
||||||
@Suppress("AssignedValueIsNeverRead")
|
|
||||||
amount = it
|
|
||||||
},
|
|
||||||
onStartClicked = {
|
|
||||||
val finalItems = if (shuffleCards) {
|
|
||||||
itemsToShow.shuffled().take(amount)
|
|
||||||
} else {
|
|
||||||
itemsToShow.take(amount)
|
|
||||||
}
|
|
||||||
onStartClicked(finalItems)
|
|
||||||
},
|
|
||||||
onClose = onClose,
|
|
||||||
selectedExerciseTypes = selectedExerciseTypes,
|
|
||||||
onExerciseTypeSelected = onExerciseTypeSelected,
|
|
||||||
hideTodayOnlySwitch = hideTodayOnlySwitch,
|
|
||||||
selectedOriginLanguage = selectedOriginLanguage,
|
|
||||||
onOriginLanguageChanged = onOriginLanguageChanged,
|
|
||||||
selectedTargetLanguage = selectedTargetLanguage,
|
|
||||||
onTargetLanguageChanged = onTargetLanguageChanged,
|
|
||||||
allItems = allItems
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun StartScreenContent(
|
|
||||||
vocabularyItemsCount: Int,
|
|
||||||
shuffleCards: Boolean,
|
|
||||||
onShuffleCardsChanged: (Boolean) -> Unit,
|
|
||||||
shuffleLanguages: Boolean,
|
|
||||||
onShuffleLanguagesChanged: (Boolean) -> Unit,
|
|
||||||
trainingMode: Boolean,
|
|
||||||
onTrainingModeChanged: (Boolean) -> Unit,
|
|
||||||
dueTodayOnly: Boolean,
|
|
||||||
onDueTodayOnlyChanged: (Boolean) -> Unit,
|
|
||||||
amount: Int,
|
|
||||||
onAmountChanged: (Int) -> Unit,
|
|
||||||
onStartClicked: () -> Unit,
|
|
||||||
onClose: () -> Unit,
|
|
||||||
selectedExerciseTypes: Set<VocabularyExerciseType>,
|
|
||||||
onExerciseTypeSelected: (VocabularyExerciseType) -> Unit,
|
|
||||||
hideTodayOnlySwitch: Boolean = false,
|
|
||||||
selectedOriginLanguage: Language?,
|
|
||||||
onOriginLanguageChanged: (Language?) -> Unit,
|
|
||||||
selectedTargetLanguage: Language?,
|
|
||||||
onTargetLanguageChanged: (Language?) -> Unit,
|
|
||||||
allItems: List<VocabularyItem>,
|
|
||||||
) {
|
|
||||||
AppScaffold(
|
|
||||||
topBar = {
|
|
||||||
AppTopAppBar(
|
|
||||||
title = stringResource(R.string.prepare_exercise),
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onClose) {
|
|
||||||
Icon(
|
|
||||||
AppIcons.Close,
|
|
||||||
contentDescription = stringResource(R.string.label_close)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
) { paddingValues ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(paddingValues)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
if (vocabularyItemsCount > 0) {
|
|
||||||
Text(stringResource(R.string.number_of_cards, amount, vocabularyItemsCount))
|
|
||||||
AppSlider(
|
|
||||||
value = amount.toFloat(),
|
|
||||||
onValueChange = { onAmountChanged(it.toInt()) },
|
|
||||||
valueRange = 1f..vocabularyItemsCount.toFloat(),
|
|
||||||
steps = if (vocabularyItemsCount > 1) vocabularyItemsCount - 2 else 0
|
|
||||||
)
|
|
||||||
|
|
||||||
// Quick selection buttons
|
|
||||||
val quickSelectValues = listOf(10, 25, 50, 100)
|
|
||||||
val availableValues =
|
|
||||||
quickSelectValues.filter { it <= vocabularyItemsCount }
|
|
||||||
|
|
||||||
if (availableValues.isNotEmpty()) {
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
|
||||||
8.dp,
|
|
||||||
Alignment.CenterHorizontally
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
availableValues.forEach { value ->
|
|
||||||
AppOutlinedButton(
|
|
||||||
onClick = { onAmountChanged(value) },
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
enabled = value <= vocabularyItemsCount
|
|
||||||
) {
|
|
||||||
Text(value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.no_cards_found_for_the_selected_filters),
|
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.padding(vertical = 24.dp),
|
|
||||||
thickness = DividerDefaults.Thickness,
|
|
||||||
color = DividerDefaults.color
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// Language Selection Section
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.label_language_direction),
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.text_language_direction_explanation),
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
|
|
||||||
val activity = LocalContext.current.findActivity()
|
|
||||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
|
|
||||||
// Get available languages from the card set
|
|
||||||
val availableLanguages = remember(allItems) {
|
|
||||||
allItems.flatMap { listOfNotNull(it.languageFirstId, it.languageSecondId) }
|
|
||||||
.distinct()
|
|
||||||
.mapNotNull { languageId ->
|
|
||||||
languageViewModel.allLanguages.value.find { it.nameResId == languageId }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
// Origin Language Dropdown
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.label_origin_language),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
|
||||||
)
|
|
||||||
SingleLanguageDropDown(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
languageViewModel = languageViewModel,
|
|
||||||
selectedLanguage = selectedOriginLanguage,
|
|
||||||
onLanguageSelected = { language ->
|
|
||||||
onOriginLanguageChanged(language)
|
|
||||||
// Clear target language if it's the same as origin
|
|
||||||
if (selectedTargetLanguage?.nameResId == language.nameResId) {
|
|
||||||
onTargetLanguageChanged(null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showNoneOption = true,
|
|
||||||
alternateLanguages = availableLanguages
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Target Language Dropdown
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.label_target_language),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
|
||||||
)
|
|
||||||
SingleLanguageDropDown(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
languageViewModel = languageViewModel,
|
|
||||||
selectedLanguage = selectedTargetLanguage,
|
|
||||||
onLanguageSelected = { language ->
|
|
||||||
onTargetLanguageChanged(language)
|
|
||||||
// Clear origin language if it's the same as target
|
|
||||||
if (selectedOriginLanguage?.nameResId == language.nameResId) {
|
|
||||||
onOriginLanguageChanged(null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
alternateLanguages = availableLanguages,
|
|
||||||
showNoneOption = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.padding(vertical = 24.dp),
|
|
||||||
thickness = DividerDefaults.Thickness,
|
|
||||||
color = DividerDefaults.color
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.label_choose_exercise_types),
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
ExerciseTypeSelector(
|
|
||||||
selectedTypes = selectedExerciseTypes,
|
|
||||||
onTypeSelected = onExerciseTypeSelected
|
|
||||||
)
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.padding(vertical = 24.dp),
|
|
||||||
thickness = DividerDefaults.Thickness,
|
|
||||||
color = DividerDefaults.color
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.options),
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
OptionItemSwitch(
|
|
||||||
title = stringResource(R.string.shuffle_cards),
|
|
||||||
description = stringResource(R.string.text_shuffle_card_order_description),
|
|
||||||
checked = shuffleCards,
|
|
||||||
onCheckedChange = onShuffleCardsChanged
|
|
||||||
)
|
|
||||||
OptionItemSwitch(
|
|
||||||
title = stringResource(R.string.text_shuffle_languages),
|
|
||||||
description = stringResource(R.string.text_shuffle_languages_description),
|
|
||||||
checked = shuffleLanguages,
|
|
||||||
onCheckedChange = onShuffleLanguagesChanged
|
|
||||||
)
|
|
||||||
OptionItemSwitch(
|
|
||||||
title = stringResource(R.string.label_training_mode),
|
|
||||||
description = stringResource(R.string.text_training_mode_description),
|
|
||||||
checked = trainingMode,
|
|
||||||
onCheckedChange = onTrainingModeChanged
|
|
||||||
)
|
|
||||||
if (!hideTodayOnlySwitch) {
|
|
||||||
OptionItemSwitch(
|
|
||||||
title = stringResource(R.string.text_due_today_only),
|
|
||||||
description = stringResource(R.string.text_due_today_only_description),
|
|
||||||
checked = dueTodayOnly,
|
|
||||||
onCheckedChange = onDueTodayOnlyChanged
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(16.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AppButton(
|
|
||||||
onClick = onStartClicked,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
.height(50.dp),
|
|
||||||
enabled = vocabularyItemsCount > 0 && amount > 0
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.label_start_exercise_2d, amount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ExerciseTypeSelector(
|
|
||||||
selectedTypes: Set<VocabularyExerciseType>,
|
|
||||||
onTypeSelected: (VocabularyExerciseType) -> Unit,
|
|
||||||
) {
|
|
||||||
// Using FlowRow for a more flexible layout that wraps to the next line if needed
|
|
||||||
FlowRow(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
ExerciseTypeCard(
|
|
||||||
icon = AppIcons.Guessing,
|
|
||||||
isSelected = VocabularyExerciseType.GUESSING in selectedTypes,
|
|
||||||
onClick = { onTypeSelected(VocabularyExerciseType.GUESSING) },
|
|
||||||
text = stringResource(R.string.label_guessing_exercise),
|
|
||||||
)
|
|
||||||
ExerciseTypeCard(
|
|
||||||
icon = AppIcons.SpellCheck,
|
|
||||||
isSelected = VocabularyExerciseType.SPELLING in selectedTypes,
|
|
||||||
onClick = { onTypeSelected(VocabularyExerciseType.SPELLING) },
|
|
||||||
text = stringResource(R.string.label_spelling_exercise),
|
|
||||||
)
|
|
||||||
ExerciseTypeCard(
|
|
||||||
icon = AppIcons.CheckList,
|
|
||||||
isSelected = VocabularyExerciseType.MULTIPLE_CHOICE in selectedTypes,
|
|
||||||
onClick = { onTypeSelected(VocabularyExerciseType.MULTIPLE_CHOICE) },
|
|
||||||
text = stringResource(R.string.label_multiple_choice_exercise),
|
|
||||||
)
|
|
||||||
ExerciseTypeCard(
|
|
||||||
icon = AppIcons.Extension,
|
|
||||||
isSelected = VocabularyExerciseType.WORD_JUMBLE in selectedTypes,
|
|
||||||
onClick = { onTypeSelected(VocabularyExerciseType.WORD_JUMBLE) },
|
|
||||||
text = stringResource(R.string.label_word_jumble_exercise),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ExerciseTypeCard(
|
|
||||||
text: String,
|
|
||||||
icon: ImageVector,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
) {
|
|
||||||
val borderColor by animateColorAsState(
|
|
||||||
targetValue = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline.copy(
|
|
||||||
alpha = 0.5f
|
|
||||||
),
|
|
||||||
label = "borderColorAnimation",
|
|
||||||
animationSpec = tween(300)
|
|
||||||
)
|
|
||||||
val containerColor by animateColorAsState(
|
|
||||||
targetValue = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
animationSpec = tween(300)
|
|
||||||
)
|
|
||||||
|
|
||||||
Card(
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = Modifier.size(width = 120.dp, height = 100.dp), // Made the cards smaller
|
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
border = BorderStroke(2.dp, borderColor),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = containerColor),
|
|
||||||
elevation = CardDefaults.cardElevation(if (isSelected) 4.dp else 1.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center
|
|
||||||
) {
|
|
||||||
Icon(icon, contentDescription = null, modifier = Modifier.size(32.dp)) // Smaller icon
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
style = MaterialTheme.typography.bodyLarge, // Smaller text
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,6 @@ import eu.gaudian.translator.view.dialogs.CategorySelectionDialog
|
|||||||
import eu.gaudian.translator.view.dialogs.StageSelectionDialog
|
import eu.gaudian.translator.view.dialogs.StageSelectionDialog
|
||||||
import eu.gaudian.translator.view.vocabulary.card.VocabularyDisplayCard
|
import eu.gaudian.translator.view.vocabulary.card.VocabularyDisplayCard
|
||||||
import eu.gaudian.translator.view.vocabulary.card.VocabularyExerciseCard
|
import eu.gaudian.translator.view.vocabulary.card.VocabularyExerciseCard
|
||||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
|
||||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
@@ -68,7 +67,6 @@ fun VocabularyCardHost(
|
|||||||
) {
|
) {
|
||||||
val activity = LocalContext.current.findActivity()
|
val activity = LocalContext.current.findActivity()
|
||||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val navigationItems by vocabularyViewModel.currentNavigationItems.collectAsState()
|
val navigationItems by vocabularyViewModel.currentNavigationItems.collectAsState()
|
||||||
@@ -148,7 +146,6 @@ fun VocabularyCardHost(
|
|||||||
var showStatisticsDialog by remember { mutableStateOf(false) }
|
var showStatisticsDialog by remember { mutableStateOf(false) }
|
||||||
var showCategoryDialog by remember { mutableStateOf(false) }
|
var showCategoryDialog by remember { mutableStateOf(false) }
|
||||||
var showStageDialog by remember { mutableStateOf(false) }
|
var showStageDialog by remember { mutableStateOf(false) }
|
||||||
var showImportDialog by remember { mutableStateOf(false) }
|
|
||||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(currentVocabularyItem.id) {
|
LaunchedEffect(currentVocabularyItem.id) {
|
||||||
isEditing = false
|
isEditing = false
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("HardCodedStringLiteral")
|
||||||
|
|
||||||
package eu.gaudian.translator.view.vocabulary
|
package eu.gaudian.translator.view.vocabulary
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ private fun MonthGrid(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
val locale = java.util.Locale.getDefault()
|
val locale = getDefault()
|
||||||
// Generate localized short weekday labels for Monday to Sunday.
|
// Generate localized short weekday labels for Monday to Sunday.
|
||||||
val dayFormatter = remember(locale) {
|
val dayFormatter = remember(locale) {
|
||||||
DateTimeFormatter.ofPattern("EEEEE", locale)
|
DateTimeFormatter.ofPattern("EEEEE", locale)
|
||||||
|
|||||||
@@ -295,7 +295,6 @@ fun VocabularySortingItem(
|
|||||||
val activity = LocalContext.current.findActivity()
|
val activity = LocalContext.current.findActivity()
|
||||||
val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
|
||||||
var wordFirst by remember { mutableStateOf(item.wordFirst) }
|
var wordFirst by remember { mutableStateOf(item.wordFirst) }
|
||||||
var wordSecond by remember { mutableStateOf(item.wordSecond) }
|
var wordSecond by remember { mutableStateOf(item.wordSecond) }
|
||||||
var selectedCategories by remember { mutableStateOf<List<Int>>(emptyList()) }
|
var selectedCategories by remember { mutableStateOf<List<Int>>(emptyList()) }
|
||||||
@@ -310,7 +309,6 @@ fun VocabularySortingItem(
|
|||||||
var articlesLangSecond by remember { mutableStateOf(emptySet<String>()) }
|
var articlesLangSecond by remember { mutableStateOf(emptySet<String>()) }
|
||||||
|
|
||||||
var showDuplicateDialog by remember { mutableStateOf(false) }
|
var showDuplicateDialog by remember { mutableStateOf(false) }
|
||||||
val categories by categoryViewModel.categories.collectAsState(initial = emptyList())
|
|
||||||
|
|
||||||
// NEW: Calculate if the item is valid for the "Done" button in faulty mode
|
// NEW: Calculate if the item is valid for the "Done" button in faulty mode
|
||||||
val isItemNowValid by remember(wordFirst, wordSecond, langFirst, langSecond) {
|
val isItemNowValid by remember(wordFirst, wordSecond, langFirst, langSecond) {
|
||||||
|
|||||||
@@ -134,36 +134,6 @@ fun VocabularyExerciseCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("We need to seperate this into two: one for display and one for exercises")
|
|
||||||
@Composable
|
|
||||||
fun VocabularyCard(
|
|
||||||
vocabularyItem: VocabularyItem,
|
|
||||||
navController: NavController,
|
|
||||||
exerciseMode: Boolean,
|
|
||||||
isFlipped: Boolean,
|
|
||||||
switchOrder: Boolean,
|
|
||||||
onStatisticsClick: () -> Unit = {},
|
|
||||||
onMoveToCategoryClick: () -> Unit = {},
|
|
||||||
onMoveToStageClick: () -> Unit = {},
|
|
||||||
onDeleteClick: () -> Unit = {},
|
|
||||||
userSpellingAnswer: String? = null,
|
|
||||||
isUserSpellingCorrect: Boolean? = null,
|
|
||||||
) {
|
|
||||||
VocabularyCardContent(
|
|
||||||
vocabularyItem = vocabularyItem,
|
|
||||||
navController = navController,
|
|
||||||
isExerciseMode = exerciseMode,
|
|
||||||
isFlipped = isFlipped,
|
|
||||||
switchOrder = switchOrder,
|
|
||||||
onStatisticsClick = onStatisticsClick,
|
|
||||||
onMoveToCategoryClick = onMoveToCategoryClick,
|
|
||||||
onMoveToStageClick = onMoveToStageClick,
|
|
||||||
onDeleteClick = onDeleteClick,
|
|
||||||
userSpellingAnswer = userSpellingAnswer,
|
|
||||||
isUserSpellingCorrect = isUserSpellingCorrect,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun VocabularyCardContent(
|
private fun VocabularyCardContent(
|
||||||
vocabularyItem: VocabularyItem,
|
vocabularyItem: VocabularyItem,
|
||||||
|
|||||||
@@ -995,7 +995,7 @@ class DictionaryViewModel @Inject constructor(
|
|||||||
* Returns true if data is still loading (null).
|
* Returns true if data is still loading (null).
|
||||||
*/
|
*/
|
||||||
fun getStructuredDictionaryDataLoading(entry: DictionaryWordEntry): StateFlow<Boolean> {
|
fun getStructuredDictionaryDataLoading(entry: DictionaryWordEntry): StateFlow<Boolean> {
|
||||||
val key = entry.word + "_" + entry.langCode
|
entry.word + "_" + entry.langCode
|
||||||
// Create a derived flow that emits true when data is null
|
// Create a derived flow that emits true when data is null
|
||||||
val dataFlow = getStructuredDictionaryDataState(entry)
|
val dataFlow = getStructuredDictionaryDataState(entry)
|
||||||
val loadingFlow = MutableStateFlow(true)
|
val loadingFlow = MutableStateFlow(true)
|
||||||
|
|||||||
@@ -209,13 +209,6 @@ class ExerciseViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startAdHocExercise(exercise: Exercise, questions: List<Question>) {
|
|
||||||
_exerciseSessionState.value = ExerciseSessionState(
|
|
||||||
exercise = exercise,
|
|
||||||
questions = questions
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startExercise(exercise: Exercise) {
|
fun startExercise(exercise: Exercise) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val allQuestions = exerciseRepository.getAllQuestionsFlow().first()
|
val allQuestions = exerciseRepository.getAllQuestionsFlow().first()
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ class ProgressViewModel @Inject constructor(
|
|||||||
_dailyGoal.value = dailyGoalValue
|
_dailyGoal.value = dailyGoalValue
|
||||||
|
|
||||||
// Get today's completed count
|
// Get today's completed count
|
||||||
val today = kotlin.time.Clock.System.now().toLocalDateTime(kotlinx.datetime.TimeZone.currentSystemDefault()).date
|
val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||||
val todayCompleted = vocabularyRepository.getCorrectAnswerCountForDate(today)
|
val todayCompleted = vocabularyRepository.getCorrectAnswerCountForDate(today)
|
||||||
_todayCompletedCount.value = todayCompleted
|
_todayCompletedCount.value = todayCompleted
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("HardCodedStringLiteral")
|
||||||
|
|
||||||
package eu.gaudian.translator.viewmodel
|
package eu.gaudian.translator.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
@@ -397,11 +399,6 @@ class VocabularyExerciseViewModel @Inject constructor(
|
|||||||
loadExercise()
|
loadExercise()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTrainingModeChanged(value: Boolean) {
|
|
||||||
Log.d("ExerciseVM", "onTrainingModeChanged: $value")
|
|
||||||
_trainingMode.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startExerciseWithConfig(
|
fun startExerciseWithConfig(
|
||||||
items: List<VocabularyItem>,
|
items: List<VocabularyItem>,
|
||||||
config: ExerciseConfig
|
config: ExerciseConfig
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
@@ -36,7 +36,6 @@
|
|||||||
<string name="title_show_success_message">Erfolgsmeldung anzeigen</string>
|
<string name="title_show_success_message">Erfolgsmeldung anzeigen</string>
|
||||||
<string name="label_add_category">Kategorie hinzufügen</string>
|
<string name="label_add_category">Kategorie hinzufügen</string>
|
||||||
<string name="title_settings">Einstellungen</string>
|
<string name="title_settings">Einstellungen</string>
|
||||||
<string name="title_dashboard">Dashboard</string>
|
|
||||||
<string name="title_developer_options">Entwickleroptionen</string>
|
<string name="title_developer_options">Entwickleroptionen</string>
|
||||||
<string name="title_multiple">Mehrere</string>
|
<string name="title_multiple">Mehrere</string>
|
||||||
<string name="label_translation_settings">Übersetzung</string>
|
<string name="label_translation_settings">Übersetzung</string>
|
||||||
@@ -61,7 +60,6 @@
|
|||||||
<string name="error_no_rows_to_import">Keine Zeilen zum Importieren. Bitte Spalten und Kopfzeile prüfen.</string>
|
<string name="error_no_rows_to_import">Keine Zeilen zum Importieren. Bitte Spalten und Kopfzeile prüfen.</string>
|
||||||
<string name="info_imported_items_from">%1$d Vokabeln importiert.</string>
|
<string name="info_imported_items_from">%1$d Vokabeln importiert.</string>
|
||||||
<string name="label_import">Importieren</string>
|
<string name="label_import">Importieren</string>
|
||||||
<string name="menu_import_vocabulary">Vokabular mit KI erstellen</string>
|
|
||||||
<string name="menu_create_youtube_exercise">YouTube-Übung erstellen</string>
|
<string name="menu_create_youtube_exercise">YouTube-Übung erstellen</string>
|
||||||
<string name="text_youtube_link">YouTube-Link</string>
|
<string name="text_youtube_link">YouTube-Link</string>
|
||||||
<string name="text_customize_the_intervals">Passe die Intervalle und Kriterien für das Verschieben von Vokabeln an. Karten in niedrigeren Stufen werden öfter abgefragt.</string>
|
<string name="text_customize_the_intervals">Passe die Intervalle und Kriterien für das Verschieben von Vokabeln an. Karten in niedrigeren Stufen werden öfter abgefragt.</string>
|
||||||
@@ -94,10 +92,7 @@
|
|||||||
<string name="text_loading_3d">Laden…</string>
|
<string name="text_loading_3d">Laden…</string>
|
||||||
<string name="text_show_loading">Laden anzeigen</string>
|
<string name="text_show_loading">Laden anzeigen</string>
|
||||||
<string name="text_cancel_loading">Laden abbrechen</string>
|
<string name="text_cancel_loading">Laden abbrechen</string>
|
||||||
<string name="text_sentence_this_is_an_info_message">Dies ist eine Info-Nachricht.</string>
|
|
||||||
<string name="text_show_info_message">Info-Nachricht anzeigen</string>
|
<string name="text_show_info_message">Info-Nachricht anzeigen</string>
|
||||||
<string name="text_success_em">Erfolg!</string>
|
|
||||||
<string name="text_sentence_oops_something_went_wrong">Hoppla! Etwas ist schiefgegangen.</string>
|
|
||||||
<string name="text_show_error_message">Fehlermeldung anzeigen</string>
|
<string name="text_show_error_message">Fehlermeldung anzeigen</string>
|
||||||
<string name="text_reset_intro">Intro zurücksetzen</string>
|
<string name="text_reset_intro">Intro zurücksetzen</string>
|
||||||
<string name="text_sentenc_version_information_not_available">Versionsinformation nicht verfügbar.</string>
|
<string name="text_sentenc_version_information_not_available">Versionsinformation nicht verfügbar.</string>
|
||||||
@@ -127,7 +122,6 @@
|
|||||||
<string name="text_enter_api_key">API-Schlüssel eingeben</string>
|
<string name="text_enter_api_key">API-Schlüssel eingeben</string>
|
||||||
<string name="text_save_key">Schlüssel speichern</string>
|
<string name="text_save_key">Schlüssel speichern</string>
|
||||||
<string name="text_select_model">Modell auswählen</string>
|
<string name="text_select_model">Modell auswählen</string>
|
||||||
<string name="title_title_preview_title">Vorschau-Titel</string>
|
|
||||||
<string name="text_none">Keine</string>
|
<string name="text_none">Keine</string>
|
||||||
<string name="text_manual_vocabulary_list">Manuelle Vokabelliste</string>
|
<string name="text_manual_vocabulary_list">Manuelle Vokabelliste</string>
|
||||||
<string name="text_filter_all_items">Filter: Alle Einträge</string>
|
<string name="text_filter_all_items">Filter: Alle Einträge</string>
|
||||||
@@ -193,7 +187,6 @@
|
|||||||
<string name="text_difficulty_2d">Schwierigkeit: %1$s</string>
|
<string name="text_difficulty_2d">Schwierigkeit: %1$s</string>
|
||||||
<string name="text_amount_2d_questions">Anzahl: %1$d Fragen</string>
|
<string name="text_amount_2d_questions">Anzahl: %1$d Fragen</string>
|
||||||
<string name="text_generate">Erstellen</string>
|
<string name="text_generate">Erstellen</string>
|
||||||
<string name="text_let_ai_find_vocabulary_for_you">Lass die KI Vokabeln für dich finden!</string>
|
|
||||||
<string name="text_search_term">Suchbegriff</string>
|
<string name="text_search_term">Suchbegriff</string>
|
||||||
<string name="text_hint">Tipp</string>
|
<string name="text_hint">Tipp</string>
|
||||||
<string name="text_select_languages">Sprachen auswählen</string>
|
<string name="text_select_languages">Sprachen auswählen</string>
|
||||||
@@ -225,8 +218,6 @@
|
|||||||
<string name="cd_target_met">Ziel erreicht</string>
|
<string name="cd_target_met">Ziel erreicht</string>
|
||||||
<string name="text_no_vocabulary_due_today">Heute keine Vokabeln fällig</string>
|
<string name="text_no_vocabulary_due_today">Heute keine Vokabeln fällig</string>
|
||||||
<string name="text_view_all">Alle ansehen</string>
|
<string name="text_view_all">Alle ansehen</string>
|
||||||
<string name="text_custom_exercise">Eigene Übung</string>
|
|
||||||
<string name="text_daily_exercise">Tägliche Übung</string>
|
|
||||||
<string name="label_total_words">Wörter gesamt</string>
|
<string name="label_total_words">Wörter gesamt</string>
|
||||||
<string name="label_learned">Gelernt</string>
|
<string name="label_learned">Gelernt</string>
|
||||||
<string name="remaining">Übrig</string>
|
<string name="remaining">Übrig</string>
|
||||||
@@ -235,7 +226,6 @@
|
|||||||
<string name="label_learning_criteria">Lernkriterien</string>
|
<string name="label_learning_criteria">Lernkriterien</string>
|
||||||
<string name="min_correct_to_advance">Min. richtig zum Aufsteigen</string>
|
<string name="min_correct_to_advance">Min. richtig zum Aufsteigen</string>
|
||||||
<string name="max_wrong_to_demote">Max. falsch zum Absteigen</string>
|
<string name="max_wrong_to_demote">Max. falsch zum Absteigen</string>
|
||||||
<string name="daily_learning_goal">Tägliches Lernziel</string>
|
|
||||||
<string name="label_backup_and_restore">Sicherung & Wiederherstellung</string>
|
<string name="label_backup_and_restore">Sicherung & Wiederherstellung</string>
|
||||||
<string name="export_vocabulary_data">Vokabeldaten exportieren</string>
|
<string name="export_vocabulary_data">Vokabeldaten exportieren</string>
|
||||||
<string name="import_vocabulary_data">Vokabeldaten importieren</string>
|
<string name="import_vocabulary_data">Vokabeldaten importieren</string>
|
||||||
@@ -333,7 +323,6 @@
|
|||||||
<string name="last_incorrect">Zuletzt falsch: %1$s</string>
|
<string name="last_incorrect">Zuletzt falsch: %1$s</string>
|
||||||
<string name="correct_answers_">Richtige Antworten: %1$d</string>
|
<string name="correct_answers_">Richtige Antworten: %1$d</string>
|
||||||
<string name="incorrect_answers">Falsche Antworten: %1$d</string>
|
<string name="incorrect_answers">Falsche Antworten: %1$d</string>
|
||||||
<string name="label_card_with_position">Karte (%1$d/%2$d)</string>
|
|
||||||
<string name="item_id">Eintrags-ID: %1$d</string>
|
<string name="item_id">Eintrags-ID: %1$d</string>
|
||||||
<string name="statistics_are_loading">Statistiken werden geladen…</string>
|
<string name="statistics_are_loading">Statistiken werden geladen…</string>
|
||||||
<string name="to_d">nach %1$s</string>
|
<string name="to_d">nach %1$s</string>
|
||||||
@@ -364,7 +353,6 @@
|
|||||||
<string name="more_actions">Mehr Aktionen</string>
|
<string name="more_actions">Mehr Aktionen</string>
|
||||||
<string name="select_all">Alle auswählen</string>
|
<string name="select_all">Alle auswählen</string>
|
||||||
<string name="deselect_all">Auswahl aufheben</string>
|
<string name="deselect_all">Auswahl aufheben</string>
|
||||||
<string name="search_vocabulary">Vokabular suchen…</string>
|
|
||||||
<string name="no_vocabulary_items_found_perhaps_try_changing_the_filters">Keine Vokabeln gefunden. Vielleicht die Filter ändern?</string>
|
<string name="no_vocabulary_items_found_perhaps_try_changing_the_filters">Keine Vokabeln gefunden. Vielleicht die Filter ändern?</string>
|
||||||
<string name="label_category_2d">Kategorie: %1$s</string>
|
<string name="label_category_2d">Kategorie: %1$s</string>
|
||||||
<string name="repository_state_imported_from">Repository-Status importiert von %1$s</string>
|
<string name="repository_state_imported_from">Repository-Status importiert von %1$s</string>
|
||||||
@@ -469,8 +457,6 @@
|
|||||||
<string name="text_assign_these_items_2d">Ordne diese Elemente zu:</string>
|
<string name="text_assign_these_items_2d">Ordne diese Elemente zu:</string>
|
||||||
<string name="translate_the_following_d">Übersetze Folgendes (%1$s):</string>
|
<string name="translate_the_following_d">Übersetze Folgendes (%1$s):</string>
|
||||||
<string name="label_your_translation">Deine Übersetzung</string>
|
<string name="label_your_translation">Deine Übersetzung</string>
|
||||||
<string name="this_is_a_hint">Dies ist ein Hinweis.</string>
|
|
||||||
<string name="this_is_the_main_content">Dies ist der Hauptinhalt.</string>
|
|
||||||
<string name="this_is_the_content_inside_the_card">Dies ist der Inhalt in der Karte.</string>
|
<string name="this_is_the_content_inside_the_card">Dies ist der Inhalt in der Karte.</string>
|
||||||
<string name="primary_button">Primärer Button</string>
|
<string name="primary_button">Primärer Button</string>
|
||||||
<string name="primary_with_icon">Primär mit Icon</string>
|
<string name="primary_with_icon">Primär mit Icon</string>
|
||||||
@@ -488,15 +474,12 @@
|
|||||||
<string name="text_base_url_and_example">Basis-URL (z.B. \'http://192.168.0.99:1234/\')</string>
|
<string name="text_base_url_and_example">Basis-URL (z.B. \'http://192.168.0.99:1234/\')</string>
|
||||||
<string name="label_close_selection_mode">Auswahlmodus schließen</string>
|
<string name="label_close_selection_mode">Auswahlmodus schließen</string>
|
||||||
<string name="d_selected">%1$d ausgewählt</string>
|
<string name="d_selected">%1$d ausgewählt</string>
|
||||||
<string name="search_query">Suchanfrage</string>
|
|
||||||
<string name="label_close_search">Suche schließen</string>
|
<string name="label_close_search">Suche schließen</string>
|
||||||
<string name="generate_related_vocabulary_items">Verwandte Vokabeln generieren</string>
|
|
||||||
<string name="dismiss">Verwerfen</string>
|
<string name="dismiss">Verwerfen</string>
|
||||||
<string name="edit_features_for">Merkmale für \'%1$s\' bearbeiten</string>
|
<string name="edit_features_for">Merkmale für \'%1$s\' bearbeiten</string>
|
||||||
<string name="no_grammar_configuration_found_for_this_language">Keine Grammatikkonfiguration für diese Sprache gefunden.</string>
|
<string name="no_grammar_configuration_found_for_this_language">Keine Grammatikkonfiguration für diese Sprache gefunden.</string>
|
||||||
<string name="word_type">Wortart</string>
|
<string name="word_type">Wortart</string>
|
||||||
<string name="levels">Level</string>
|
<string name="levels">Level</string>
|
||||||
<string name="quick_word_pairs">Schnelle Wortpaare</string>
|
|
||||||
<string name="stage_filter">Stufenfilter</string>
|
<string name="stage_filter">Stufenfilter</string>
|
||||||
<string name="language_pair">Sprachpaar</string>
|
<string name="language_pair">Sprachpaar</string>
|
||||||
<string name="language_filter">Sprachfilter</string>
|
<string name="language_filter">Sprachfilter</string>
|
||||||
@@ -546,10 +529,6 @@
|
|||||||
<string name="friendly">Freundlich</string>
|
<string name="friendly">Freundlich</string>
|
||||||
<string name="label_academic">Akademisch</string>
|
<string name="label_academic">Akademisch</string>
|
||||||
<string name="creative">Kreativ</string>
|
<string name="creative">Kreativ</string>
|
||||||
<string name="editing_text">Text bearbeiten: %1$s</string>
|
|
||||||
<string name="no_text_received">Kein Text empfangen!</string>
|
|
||||||
<string name="error_no_text_to_edit">Fehler: Kein Text zum Bearbeiten</string>
|
|
||||||
<string name="not_launched_with_text_to_edit">Nicht mit zu bearbeitendem Text gestartet</string>
|
|
||||||
<string name="text_a_simple_list_to">Eine einfache Liste, um deine Vokabeln manuell zu sortieren</string>
|
<string name="text_a_simple_list_to">Eine einfache Liste, um deine Vokabeln manuell zu sortieren</string>
|
||||||
<string name="settings_title_voice">Stimme</string>
|
<string name="settings_title_voice">Stimme</string>
|
||||||
<string name="default_value">Standard</string>
|
<string name="default_value">Standard</string>
|
||||||
@@ -590,21 +569,11 @@
|
|||||||
<string name="intro_if_you_need_help_you">Wenn du Hilfe brauchst, findest du Hinweise in allen Bereichen der App.</string>
|
<string name="intro_if_you_need_help_you">Wenn du Hilfe brauchst, findest du Hinweise in allen Bereichen der App.</string>
|
||||||
<string name="text_navigation_bar_labels">Navigationsleisten-Beschriftungen</string>
|
<string name="text_navigation_bar_labels">Navigationsleisten-Beschriftungen</string>
|
||||||
<string name="text_show_text_labels_on_the_main_navigation_bar">Textbeschriftungen in der Hauptnavigationsleiste anzeigen.</string>
|
<string name="text_show_text_labels_on_the_main_navigation_bar">Textbeschriftungen in der Hauptnavigationsleiste anzeigen.</string>
|
||||||
<string name="text_word_pair_settings">Wortpaar-Einstellungen</string>
|
|
||||||
<string name="text_amount_of_questions_2d">Anzahl der Fragen: %1$d</string>
|
|
||||||
<string name="text_shuffle_questions">Fragen mischen</string>
|
|
||||||
<string name="tetx_training_mode">Trainingsmodus</string>
|
|
||||||
<string name="text_match_the_pairs">Bilde die Paare</string>
|
|
||||||
<string name="text_word_pair_exercise">Wortpaar-Übung</string>
|
|
||||||
<string name="text_training_mode_description">Trainingsmodus ist aktiv: Antworten beeinflussen den Fortschritt nicht.</string>
|
<string name="text_training_mode_description">Trainingsmodus ist aktiv: Antworten beeinflussen den Fortschritt nicht.</string>
|
||||||
<string name="text_days">" Tage"</string>
|
<string name="text_days">" Tage"</string>
|
||||||
<string name="label_add_vocabulary">Vokabel hinzufügen</string>
|
<string name="label_add_vocabulary">Vokabel hinzufügen</string>
|
||||||
<string name="label_create_vocabulary_with_ai">Vokabular mit KI erstellen</string>
|
|
||||||
<string name="text_vocab_empty">Keine Vokabeln gefunden. Jetzt hinzufügen?</string>
|
|
||||||
<string name="text_this_will_remove_all">Dadurch werden alle konfigurierten API-Anbieter, Modelle und gespeicherten API-Schlüssel entfernt. Diese Aktion kann nicht rückgängig gemacht werden.</string>
|
<string name="text_this_will_remove_all">Dadurch werden alle konfigurierten API-Anbieter, Modelle und gespeicherten API-Schlüssel entfernt. Diese Aktion kann nicht rückgängig gemacht werden.</string>
|
||||||
<string name="text_delete_all_providers_and_models_qm">Alle Anbieter und Modelle löschen?</string>
|
<string name="text_delete_all_providers_and_models_qm">Alle Anbieter und Modelle löschen?</string>
|
||||||
<string name="text_swap_sides">Seiten tauschen</string>
|
|
||||||
<string name="text_no_progress">Kein Fortschritt</string>
|
|
||||||
<string name="text_theme_preview">Theme-Vorschau</string>
|
<string name="text_theme_preview">Theme-Vorschau</string>
|
||||||
<string name="text_sample_word">Beispielwort</string>
|
<string name="text_sample_word">Beispielwort</string>
|
||||||
<string name="toggle_use_libretranslate">Übersetungs-Server verwenden</string>
|
<string name="toggle_use_libretranslate">Übersetungs-Server verwenden</string>
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
<string name="title_show_success_message">Mostrar Mensagem de Sucesso</string>
|
<string name="title_show_success_message">Mostrar Mensagem de Sucesso</string>
|
||||||
<string name="label_add_category">Adicionar Categoria</string>
|
<string name="label_add_category">Adicionar Categoria</string>
|
||||||
<string name="title_settings">Configurações</string>
|
<string name="title_settings">Configurações</string>
|
||||||
<string name="title_dashboard">Painel</string>
|
|
||||||
<string name="title_developer_options">Opções do Desenvolvedor</string>
|
<string name="title_developer_options">Opções do Desenvolvedor</string>
|
||||||
<string name="title_multiple">Múltiplos</string>
|
<string name="title_multiple">Múltiplos</string>
|
||||||
<string name="label_translation_settings">Configurações de Tradução</string>
|
<string name="label_translation_settings">Configurações de Tradução</string>
|
||||||
@@ -61,7 +60,6 @@
|
|||||||
<string name="error_no_rows_to_import">Nenhuma linha para importar. Verifique as colunas e o cabeçalho.</string>
|
<string name="error_no_rows_to_import">Nenhuma linha para importar. Verifique as colunas e o cabeçalho.</string>
|
||||||
<string name="info_imported_items_from">%1$d itens de vocabulário importados.</string>
|
<string name="info_imported_items_from">%1$d itens de vocabulário importados.</string>
|
||||||
<string name="label_import">Importar</string>
|
<string name="label_import">Importar</string>
|
||||||
<string name="menu_import_vocabulary">Gerar vocabulário com IA</string>
|
|
||||||
<string name="menu_create_youtube_exercise">Criar Exercício do YouTube</string>
|
<string name="menu_create_youtube_exercise">Criar Exercício do YouTube</string>
|
||||||
<string name="text_youtube_link">Link do YouTube</string>
|
<string name="text_youtube_link">Link do YouTube</string>
|
||||||
<string name="text_customize_the_intervals">Personalize os intervalos e critérios para mover os cartões de vocabulário. Cartões em estágios iniciais são perguntados com mais frequência.</string>
|
<string name="text_customize_the_intervals">Personalize os intervalos e critérios para mover os cartões de vocabulário. Cartões em estágios iniciais são perguntados com mais frequência.</string>
|
||||||
@@ -93,10 +91,7 @@
|
|||||||
<string name="text_loading_3d">Carregando…</string>
|
<string name="text_loading_3d">Carregando…</string>
|
||||||
<string name="text_show_loading">Mostrar Carregamento</string>
|
<string name="text_show_loading">Mostrar Carregamento</string>
|
||||||
<string name="text_cancel_loading">Cancelar Carregamento</string>
|
<string name="text_cancel_loading">Cancelar Carregamento</string>
|
||||||
<string name="text_sentence_this_is_an_info_message">Esta é uma mensagem informativa.</string>
|
|
||||||
<string name="text_show_info_message">Mostrar Mensagem Informativa</string>
|
<string name="text_show_info_message">Mostrar Mensagem Informativa</string>
|
||||||
<string name="text_success_em">Sucesso!</string>
|
|
||||||
<string name="text_sentence_oops_something_went_wrong">Oops! Algo deu errado.</string>
|
|
||||||
<string name="text_show_error_message">Mostrar Mensagem de Erro</string>
|
<string name="text_show_error_message">Mostrar Mensagem de Erro</string>
|
||||||
<string name="text_reset_intro">Resetar Introdução</string>
|
<string name="text_reset_intro">Resetar Introdução</string>
|
||||||
<string name="text_sentenc_version_information_not_available">Informação de versão não disponível.</string>
|
<string name="text_sentenc_version_information_not_available">Informação de versão não disponível.</string>
|
||||||
@@ -125,7 +120,6 @@
|
|||||||
<string name="text_enter_api_key">Inserir Chave de API</string>
|
<string name="text_enter_api_key">Inserir Chave de API</string>
|
||||||
<string name="text_save_key">Salvar Chave</string>
|
<string name="text_save_key">Salvar Chave</string>
|
||||||
<string name="text_select_model">Selecionar Modelo</string>
|
<string name="text_select_model">Selecionar Modelo</string>
|
||||||
<string name="title_title_preview_title">Título de Prévia</string>
|
|
||||||
<string name="text_none">Nenhum</string>
|
<string name="text_none">Nenhum</string>
|
||||||
<string name="text_manual_vocabulary_list">Lista de vocabulário manual</string>
|
<string name="text_manual_vocabulary_list">Lista de vocabulário manual</string>
|
||||||
<string name="text_filter_all_items">Filtro: Todos os itens</string>
|
<string name="text_filter_all_items">Filtro: Todos os itens</string>
|
||||||
@@ -191,7 +185,6 @@
|
|||||||
<string name="text_difficulty_2d">Dificuldade: %1$s</string>
|
<string name="text_difficulty_2d">Dificuldade: %1$s</string>
|
||||||
<string name="text_amount_2d_questions">Quantidade: %1$d Perguntas</string>
|
<string name="text_amount_2d_questions">Quantidade: %1$d Perguntas</string>
|
||||||
<string name="text_generate">Gerar</string>
|
<string name="text_generate">Gerar</string>
|
||||||
<string name="text_let_ai_find_vocabulary_for_you">Deixe a IA encontrar vocabulário para você!</string>
|
|
||||||
<string name="text_search_term">Termo de Busca</string>
|
<string name="text_search_term">Termo de Busca</string>
|
||||||
<string name="text_select_languages">Selecionar Idiomas</string>
|
<string name="text_select_languages">Selecionar Idiomas</string>
|
||||||
<string name="text_select_amount">Selecionar Quantidade</string>
|
<string name="text_select_amount">Selecionar Quantidade</string>
|
||||||
@@ -222,8 +215,6 @@
|
|||||||
<string name="cd_target_met">Meta Atingida</string>
|
<string name="cd_target_met">Meta Atingida</string>
|
||||||
<string name="text_no_vocabulary_due_today">Nenhum Vocabulário para Hoje</string>
|
<string name="text_no_vocabulary_due_today">Nenhum Vocabulário para Hoje</string>
|
||||||
<string name="text_view_all">Ver Todos</string>
|
<string name="text_view_all">Ver Todos</string>
|
||||||
<string name="text_custom_exercise">Exercício Personalizado</string>
|
|
||||||
<string name="text_daily_exercise">Exercício Diário</string>
|
|
||||||
<string name="label_total_words">Total de Palavras</string>
|
<string name="label_total_words">Total de Palavras</string>
|
||||||
<string name="label_learned">Aprendidas</string>
|
<string name="label_learned">Aprendidas</string>
|
||||||
<string name="remaining">Restantes</string>
|
<string name="remaining">Restantes</string>
|
||||||
@@ -232,8 +223,7 @@
|
|||||||
<string name="label_learning_criteria">Critérios de Aprendizagem</string>
|
<string name="label_learning_criteria">Critérios de Aprendizagem</string>
|
||||||
<string name="min_correct_to_advance">Mín. de Acertos para Avançar</string>
|
<string name="min_correct_to_advance">Mín. de Acertos para Avançar</string>
|
||||||
<string name="max_wrong_to_demote">Máx. de Erros para Regredir</string>
|
<string name="max_wrong_to_demote">Máx. de Erros para Regredir</string>
|
||||||
<string name="daily_learning_goal">Meta de Aprendizagem Diária</string>
|
<string name="label_target_correct_answers_per_day">Meta de Respostas Corretas por Dia</string>
|
||||||
<string name="target_correct_answers_per_day">Meta de Respostas Corretas por Dia</string>
|
|
||||||
<string name="label_backup_and_restore">Backup e Restauração</string>
|
<string name="label_backup_and_restore">Backup e Restauração</string>
|
||||||
<string name="export_vocabulary_data">Exportar Dados do Vocabulário</string>
|
<string name="export_vocabulary_data">Exportar Dados do Vocabulário</string>
|
||||||
<string name="import_vocabulary_data">Importar Dados do Vocabulário</string>
|
<string name="import_vocabulary_data">Importar Dados do Vocabulário</string>
|
||||||
@@ -332,7 +322,6 @@
|
|||||||
<string name="last_incorrect">Último erro: %1$s</string>
|
<string name="last_incorrect">Último erro: %1$s</string>
|
||||||
<string name="correct_answers_">Respostas corretas: %1$d</string>
|
<string name="correct_answers_">Respostas corretas: %1$d</string>
|
||||||
<string name="incorrect_answers">Respostas incorretas: %1$d</string>
|
<string name="incorrect_answers">Respostas incorretas: %1$d</string>
|
||||||
<string name="label_card_with_position">Cartão (%1$d/%2$d)</string>
|
|
||||||
<string name="item_id">ID do Item: %1$d</string>
|
<string name="item_id">ID do Item: %1$d</string>
|
||||||
<string name="statistics_are_loading">Carregando estatísticas…</string>
|
<string name="statistics_are_loading">Carregando estatísticas…</string>
|
||||||
<string name="to_d">para %1$s</string>
|
<string name="to_d">para %1$s</string>
|
||||||
@@ -363,7 +352,6 @@
|
|||||||
<string name="more_actions">Mais ações</string>
|
<string name="more_actions">Mais ações</string>
|
||||||
<string name="select_all">Selecionar Tudo</string>
|
<string name="select_all">Selecionar Tudo</string>
|
||||||
<string name="deselect_all">Desmarcar Tudo</string>
|
<string name="deselect_all">Desmarcar Tudo</string>
|
||||||
<string name="search_vocabulary">Pesquisar vocabulário…</string>
|
|
||||||
<string name="no_vocabulary_items_found_perhaps_try_changing_the_filters">Nenhum item de vocabulário encontrado. Que tal tentar mudar os filtros?</string>
|
<string name="no_vocabulary_items_found_perhaps_try_changing_the_filters">Nenhum item de vocabulário encontrado. Que tal tentar mudar os filtros?</string>
|
||||||
<string name="label_category_2d">Categoria: %1$s</string>
|
<string name="label_category_2d">Categoria: %1$s</string>
|
||||||
<string name="repository_state_imported_from">Estado do repositório importado de %1$s</string>
|
<string name="repository_state_imported_from">Estado do repositório importado de %1$s</string>
|
||||||
@@ -467,8 +455,6 @@
|
|||||||
<string name="text_assign_these_items_2d">Associe estes itens:</string>
|
<string name="text_assign_these_items_2d">Associe estes itens:</string>
|
||||||
<string name="translate_the_following_d">Traduza o seguinte (%1$s):</string>
|
<string name="translate_the_following_d">Traduza o seguinte (%1$s):</string>
|
||||||
<string name="label_your_translation">Sua tradução</string>
|
<string name="label_your_translation">Sua tradução</string>
|
||||||
<string name="this_is_a_hint">Esta é uma dica.</string>
|
|
||||||
<string name="this_is_the_main_content">Este é o conteúdo principal.</string>
|
|
||||||
<string name="this_is_the_content_inside_the_card">Este é o conteúdo dentro do cartão.</string>
|
<string name="this_is_the_content_inside_the_card">Este é o conteúdo dentro do cartão.</string>
|
||||||
<string name="primary_button">Botão Primário</string>
|
<string name="primary_button">Botão Primário</string>
|
||||||
<string name="primary_with_icon">Primário com Ícone</string>
|
<string name="primary_with_icon">Primário com Ícone</string>
|
||||||
@@ -486,15 +472,12 @@
|
|||||||
<string name="text_base_url_and_example">URL Base (ex: \'http://192.168.0.99:1234/\')</string>
|
<string name="text_base_url_and_example">URL Base (ex: \'http://192.168.0.99:1234/\')</string>
|
||||||
<string name="label_close_selection_mode">Fechar modo de seleção</string>
|
<string name="label_close_selection_mode">Fechar modo de seleção</string>
|
||||||
<string name="d_selected">%1$d Selecionado(s)</string>
|
<string name="d_selected">%1$d Selecionado(s)</string>
|
||||||
<string name="search_query">Termo de pesquisa</string>
|
|
||||||
<string name="label_close_search">Fechar pesquisa</string>
|
<string name="label_close_search">Fechar pesquisa</string>
|
||||||
<string name="generate_related_vocabulary_items">Gerar itens de vocabulário relacionados</string>
|
|
||||||
<string name="dismiss">Dispensar</string>
|
<string name="dismiss">Dispensar</string>
|
||||||
<string name="edit_features_for">Editar Recursos para \'%1$s\'</string>
|
<string name="edit_features_for">Editar Recursos para \'%1$s\'</string>
|
||||||
<string name="no_grammar_configuration_found_for_this_language">Nenhuma configuração de gramática encontrada para este idioma.</string>
|
<string name="no_grammar_configuration_found_for_this_language">Nenhuma configuração de gramática encontrada para este idioma.</string>
|
||||||
<string name="word_type">Tipo de Palavra</string>
|
<string name="word_type">Tipo de Palavra</string>
|
||||||
<string name="levels">Níveis</string>
|
<string name="levels">Níveis</string>
|
||||||
<string name="quick_word_pairs">Pares de palavras rápidos</string>
|
|
||||||
<string name="stage_filter">Filtro de Estágio</string>
|
<string name="stage_filter">Filtro de Estágio</string>
|
||||||
<string name="language_pair">Par de Idiomas</string>
|
<string name="language_pair">Par de Idiomas</string>
|
||||||
<string name="language_filter">Filtro de Idioma</string>
|
<string name="language_filter">Filtro de Idioma</string>
|
||||||
@@ -544,10 +527,6 @@
|
|||||||
<string name="friendly">Amigável</string>
|
<string name="friendly">Amigável</string>
|
||||||
<string name="label_academic">Acadêmico</string>
|
<string name="label_academic">Acadêmico</string>
|
||||||
<string name="creative">Criativo</string>
|
<string name="creative">Criativo</string>
|
||||||
<string name="editing_text">Editando Texto: %1$s</string>
|
|
||||||
<string name="no_text_received">Nenhum texto recebido!</string>
|
|
||||||
<string name="error_no_text_to_edit">Erro: Nenhum texto para editar</string>
|
|
||||||
<string name="not_launched_with_text_to_edit">Não iniciado com texto para editar</string>
|
|
||||||
<string name="text_a_simple_list_to">Uma lista simples para organizar o seu vocabulário manualmente</string>
|
<string name="text_a_simple_list_to">Uma lista simples para organizar o seu vocabulário manualmente</string>
|
||||||
<string name="settings_title_voice">Voz</string>
|
<string name="settings_title_voice">Voz</string>
|
||||||
<string name="default_value">Padrão</string>
|
<string name="default_value">Padrão</string>
|
||||||
@@ -588,21 +567,11 @@
|
|||||||
<string name="intro_if_you_need_help_you">Se precisar de ajuda, você pode encontrar dicas em todas as seções do aplicativo.</string>
|
<string name="intro_if_you_need_help_you">Se precisar de ajuda, você pode encontrar dicas em todas as seções do aplicativo.</string>
|
||||||
<string name="text_navigation_bar_labels">Rótulos da Barra de Navegação</string>
|
<string name="text_navigation_bar_labels">Rótulos da Barra de Navegação</string>
|
||||||
<string name="text_show_text_labels_on_the_main_navigation_bar">Mostrar rótulos de texto na barra de navegação principal.</string>
|
<string name="text_show_text_labels_on_the_main_navigation_bar">Mostrar rótulos de texto na barra de navegação principal.</string>
|
||||||
<string name="text_word_pair_settings">Configurações de Pares de Palavras</string>
|
|
||||||
<string name="text_amount_of_questions_2d">Quantidade de perguntas: %1$d</string>
|
|
||||||
<string name="text_shuffle_questions">Embaralhar perguntas</string>
|
|
||||||
<string name="tetx_training_mode">Modo de treino</string>
|
|
||||||
<string name="text_match_the_pairs">Combine os pares</string>
|
|
||||||
<string name="text_word_pair_exercise">Exercício de Pares de Palavras</string>
|
|
||||||
<string name="text_training_mode_description">Modo de treino ativado: respostas não afetarão o progresso.</string>
|
<string name="text_training_mode_description">Modo de treino ativado: respostas não afetarão o progresso.</string>
|
||||||
<string name="text_days">" dias"</string>
|
<string name="text_days">" dias"</string>
|
||||||
<string name="label_add_vocabulary">Adicionar Vocabulário</string>
|
<string name="label_add_vocabulary">Adicionar Vocabulário</string>
|
||||||
<string name="label_create_vocabulary_with_ai">Criar Vocabulário com IA</string>
|
|
||||||
<string name="text_vocab_empty">Nenhum item de vocabulário encontrado. Adicionar agora?</string>
|
|
||||||
<string name="text_this_will_remove_all">Isso removerá todos os provedores de API, modelos e chaves de API configurados. Esta ação não pode ser desfeita.</string>
|
<string name="text_this_will_remove_all">Isso removerá todos os provedores de API, modelos e chaves de API configurados. Esta ação não pode ser desfeita.</string>
|
||||||
<string name="text_delete_all_providers_and_models_qm">Excluir todos os provedores e modelos?</string>
|
<string name="text_delete_all_providers_and_models_qm">Excluir todos os provedores e modelos?</string>
|
||||||
<string name="text_swap_sides">Trocar lados</string>
|
|
||||||
<string name="text_no_progress">Sem progresso</string>
|
|
||||||
<string name="text_theme_preview">Prévia do Tema</string>
|
<string name="text_theme_preview">Prévia do Tema</string>
|
||||||
<string name="text_sample_word">Palavra de Exemplo</string>
|
<string name="text_sample_word">Palavra de Exemplo</string>
|
||||||
<string name="toggle_use_libretranslate">Usar servidor de Tradução</string>
|
<string name="toggle_use_libretranslate">Usar servidor de Tradução</string>
|
||||||
|
|||||||
@@ -57,8 +57,6 @@
|
|||||||
<string name="d_selected">%1$d Selected</string>
|
<string name="d_selected">%1$d Selected</string>
|
||||||
<string name="d_the_quick_brown_fox_jumps_over_the_lazy_dog">%1$s: The quick brown fox jumps over the lazy dog.</string>
|
<string name="d_the_quick_brown_fox_jumps_over_the_lazy_dog">%1$s: The quick brown fox jumps over the lazy dog.</string>
|
||||||
|
|
||||||
<string name="daily_learning_goal">Daily Learning Goal</string>
|
|
||||||
|
|
||||||
<string name="danger_zone">Danger Zone</string>
|
<string name="danger_zone">Danger Zone</string>
|
||||||
|
|
||||||
<string name="days_2d">%1$d days</string>
|
<string name="days_2d">%1$d days</string>
|
||||||
@@ -96,8 +94,6 @@
|
|||||||
<string name="edit">Edit</string>
|
<string name="edit">Edit</string>
|
||||||
<string name="edit_features_for">Edit Features for \'%1$s\'</string>
|
<string name="edit_features_for">Edit Features for \'%1$s\'</string>
|
||||||
|
|
||||||
<string name="editing_text">Editing Text: %1$s</string>
|
|
||||||
|
|
||||||
<string name="email_address">Email Address</string>
|
<string name="email_address">Email Address</string>
|
||||||
<string name="email_log">Email Log</string>
|
<string name="email_log">Email Log</string>
|
||||||
|
|
||||||
@@ -106,7 +102,6 @@
|
|||||||
<string name="endpoint_e_g_api_chat">Endpoint (e.g., /v1/chat/completions/)</string>
|
<string name="endpoint_e_g_api_chat">Endpoint (e.g., /v1/chat/completions/)</string>
|
||||||
|
|
||||||
<string name="error_no_rows_to_import">No rows to import. Please check the selected columns and header row.</string>
|
<string name="error_no_rows_to_import">No rows to import. Please check the selected columns and header row.</string>
|
||||||
<string name="error_no_text_to_edit">Error: No text to edit</string>
|
|
||||||
<string name="error_parsing_table">Error parsing table</string>
|
<string name="error_parsing_table">Error parsing table</string>
|
||||||
<string name="error_parsing_table_with_reason">Error parsing table: %1$s</string>
|
<string name="error_parsing_table_with_reason">Error parsing table: %1$s</string>
|
||||||
<string name="error_select_languages">Please select two languages.</string>
|
<string name="error_select_languages">Please select two languages.</string>
|
||||||
@@ -160,8 +155,6 @@
|
|||||||
|
|
||||||
<string name="general_settings">General Settings</string>
|
<string name="general_settings">General Settings</string>
|
||||||
|
|
||||||
<string name="generate_related_vocabulary_items">Generate related vocabulary items</string>
|
|
||||||
|
|
||||||
<string name="get_started">Get Started</string>
|
<string name="get_started">Get Started</string>
|
||||||
|
|
||||||
<string name="got_it">Got it!</string>
|
<string name="got_it">Got it!</string>
|
||||||
@@ -225,13 +218,11 @@
|
|||||||
<string name="label_amount_models">%1$d models</string>
|
<string name="label_amount_models">%1$d models</string>
|
||||||
<string name="label_analyze_grammar">Analyze Grammar</string>
|
<string name="label_analyze_grammar">Analyze Grammar</string>
|
||||||
<string name="label_appearance">Appearance</string>
|
<string name="label_appearance">Appearance</string>
|
||||||
<string name="hint_settings_title_help">Help</string>
|
|
||||||
<string name="label_apply_filters">Apply Filters</string>
|
<string name="label_apply_filters">Apply Filters</string>
|
||||||
<string name="label_article">Article</string>
|
<string name="label_article">Article</string>
|
||||||
<string name="label_backup_and_restore">Backup and Restore</string>
|
<string name="label_backup_and_restore">Backup and Restore</string>
|
||||||
<string name="label_by_language">By Language</string>
|
<string name="label_by_language">By Language</string>
|
||||||
<string name="label_cancel">Cancel</string>
|
<string name="label_cancel">Cancel</string>
|
||||||
<string name="label_card_with_position">Card (%1$d/%2$d)</string>
|
|
||||||
<string name="label_casual">Casual</string>
|
<string name="label_casual">Casual</string>
|
||||||
<string name="label_categories">Categories</string>
|
<string name="label_categories">Categories</string>
|
||||||
<string name="label_category">Category</string>
|
<string name="label_category">Category</string>
|
||||||
@@ -249,7 +240,6 @@
|
|||||||
<string name="label_continue">Continue</string>
|
<string name="label_continue">Continue</string>
|
||||||
<string name="label_correct">Correct</string>
|
<string name="label_correct">Correct</string>
|
||||||
<string name="label_create_exercise">Create Exercise</string>
|
<string name="label_create_exercise">Create Exercise</string>
|
||||||
<string name="label_create_vocabulary_with_ai">Create Vocabulary with AI</string>
|
|
||||||
<string name="label_custom">Custom</string>
|
<string name="label_custom">Custom</string>
|
||||||
<string name="label_definitions">Definitions</string>
|
<string name="label_definitions">Definitions</string>
|
||||||
<string name="label_delete">Delete</string>
|
<string name="label_delete">Delete</string>
|
||||||
@@ -418,7 +408,6 @@
|
|||||||
<string name="max_wrong_to_demote">Max Wrong to Demote</string>
|
<string name="max_wrong_to_demote">Max Wrong to Demote</string>
|
||||||
|
|
||||||
<string name="menu_create_youtube_exercise">Create YouTube Exercise</string>
|
<string name="menu_create_youtube_exercise">Create YouTube Exercise</string>
|
||||||
<string name="menu_import_vocabulary">Generate vocabulary with AI</string>
|
|
||||||
|
|
||||||
<string name="merge">Merge</string>
|
<string name="merge">Merge</string>
|
||||||
<string name="merge_items">Merge Items</string>
|
<string name="merge_items">Merge Items</string>
|
||||||
@@ -461,11 +450,9 @@
|
|||||||
<string name="no_models_configured">No Models Configured</string>
|
<string name="no_models_configured">No Models Configured</string>
|
||||||
<string name="no_models_found">No models found</string>
|
<string name="no_models_found">No models found</string>
|
||||||
<string name="no_new_vocabulary_to_sort">No New Vocabulary to Sort</string>
|
<string name="no_new_vocabulary_to_sort">No New Vocabulary to Sort</string>
|
||||||
<string name="no_text_received">No text received!</string>
|
|
||||||
<string name="no_vocabulary_items_found_perhaps_try_changing_the_filters">No vocabulary items found. Perhaps try changing the filters?</string>
|
<string name="no_vocabulary_items_found_perhaps_try_changing_the_filters">No vocabulary items found. Perhaps try changing the filters?</string>
|
||||||
|
|
||||||
<string name="not_available">Not available</string>
|
<string name="not_available">Not available</string>
|
||||||
<string name="not_launched_with_text_to_edit">Not launched with text to edit</string>
|
|
||||||
|
|
||||||
<string name="number_of_cards">Number of Cards: %1$d / %2$d</string>
|
<string name="number_of_cards">Number of Cards: %1$d / %2$d</string>
|
||||||
|
|
||||||
@@ -563,8 +550,6 @@
|
|||||||
|
|
||||||
<string name="questions">%1$d questions</string>
|
<string name="questions">%1$d questions</string>
|
||||||
|
|
||||||
<string name="quick_word_pairs">Quick word pairs</string>
|
|
||||||
|
|
||||||
<string name="quit">Quit</string>
|
<string name="quit">Quit</string>
|
||||||
|
|
||||||
<string name="refresh_word_of_the_day">Refresh Word of the Day</string>
|
<string name="refresh_word_of_the_day">Refresh Word of the Day</string>
|
||||||
@@ -591,8 +576,6 @@
|
|||||||
|
|
||||||
<string name="search_for_a_word_s_origin">Search for a word\'s origin</string>
|
<string name="search_for_a_word_s_origin">Search for a word\'s origin</string>
|
||||||
<string name="search_models">Search Models</string>
|
<string name="search_models">Search Models</string>
|
||||||
<string name="search_query">Search query</string>
|
|
||||||
<string name="search_vocabulary">Search vocabulary…</string>
|
|
||||||
|
|
||||||
<string name="secondary_button">Secondary Button</string>
|
<string name="secondary_button">Secondary Button</string>
|
||||||
<string name="secondary_inverse">Secondary Inverse</string>
|
<string name="secondary_inverse">Secondary Inverse</string>
|
||||||
@@ -673,12 +656,10 @@
|
|||||||
|
|
||||||
<string name="tap_the_words_below_to_form_the_sentence">Tap the words below to form the sentence</string>
|
<string name="tap_the_words_below_to_form_the_sentence">Tap the words below to form the sentence</string>
|
||||||
|
|
||||||
<string name="target_correct_answers_per_day">Target Correct Answers Per Day</string>
|
<string name="label_target_correct_answers_per_day">Target Correct Answers Per Day</string>
|
||||||
|
|
||||||
<string name="test">Test</string>
|
<string name="test">Test</string>
|
||||||
|
|
||||||
<string name="tetx_training_mode">Training mode</string>
|
|
||||||
|
|
||||||
<string name="text_200_ok">200 OK</string>
|
<string name="text_200_ok">200 OK</string>
|
||||||
<string name="text_2d_categories_selected">%1$d categories selected</string>
|
<string name="text_2d_categories_selected">%1$d categories selected</string>
|
||||||
<string name="text_2d_languages_selected">%1$d Languages Selected</string>
|
<string name="text_2d_languages_selected">%1$d Languages Selected</string>
|
||||||
@@ -702,7 +683,6 @@
|
|||||||
<string name="text_amount_2d">Amount: %1$d</string>
|
<string name="text_amount_2d">Amount: %1$d</string>
|
||||||
<string name="text_amount_2d_questions">Amount: %1$d Questions</string>
|
<string name="text_amount_2d_questions">Amount: %1$d Questions</string>
|
||||||
<string name="text_amount_of_cards">Amount of cards</string>
|
<string name="text_amount_of_cards">Amount of cards</string>
|
||||||
<string name="text_amount_of_questions_2d">Amount of questions: %1$d</string>
|
|
||||||
<string name="text_an_unexpected_condition_was_encountered_on_the_server">An unexpected condition was encountered on the server.</string>
|
<string name="text_an_unexpected_condition_was_encountered_on_the_server">An unexpected condition was encountered on the server.</string>
|
||||||
<string name="text_an_unknown_error_occurred">An unknown error occurred.</string>
|
<string name="text_an_unknown_error_occurred">An unknown error occurred.</string>
|
||||||
<string name="text_and_many_more">And many more! …</string>
|
<string name="text_and_many_more">And many more! …</string>
|
||||||
@@ -741,9 +721,7 @@
|
|||||||
<string name="text_copy_corrected_text">Copy corrected text</string>
|
<string name="text_copy_corrected_text">Copy corrected text</string>
|
||||||
<string name="text_correct_em">Correct!</string>
|
<string name="text_correct_em">Correct!</string>
|
||||||
<string name="text_could_not_fetch_a_new_word">Could not fetch a new word.</string>
|
<string name="text_could_not_fetch_a_new_word">Could not fetch a new word.</string>
|
||||||
<string name="text_custom_exercise">Custom Exercise</string>
|
|
||||||
<string name="text_customize_the_intervals">Customize the intervals and criteria for moving vocabulary cards between stages. Cards in lower stages should be asked more often than those in higher stages.</string>
|
<string name="text_customize_the_intervals">Customize the intervals and criteria for moving vocabulary cards between stages. Cards in lower stages should be asked more often than those in higher stages.</string>
|
||||||
<string name="text_daily_exercise">Daily Exercise</string>
|
|
||||||
<string name="text_daily_goal_description">How many words do you want to answer correctly each day?</string>
|
<string name="text_daily_goal_description">How many words do you want to answer correctly each day?</string>
|
||||||
<string name="text_dark">Dark</string>
|
<string name="text_dark">Dark</string>
|
||||||
<string name="text_day_streak">Day Streak</string>
|
<string name="text_day_streak">Day Streak</string>
|
||||||
@@ -827,12 +805,10 @@
|
|||||||
<string name="text_language_direction_disabled_with_pairs">Clear language pair selection to choose a direction.</string>
|
<string name="text_language_direction_disabled_with_pairs">Clear language pair selection to choose a direction.</string>
|
||||||
<string name="text_language_options">Language Options</string>
|
<string name="text_language_options">Language Options</string>
|
||||||
<string name="text_last_7_days">Last 7 Days</string>
|
<string name="text_last_7_days">Last 7 Days</string>
|
||||||
<string name="text_let_ai_find_vocabulary_for_you">Let AI find vocabulary for you!</string>
|
|
||||||
<string name="text_light">Light</string>
|
<string name="text_light">Light</string>
|
||||||
<string name="text_list">List</string>
|
<string name="text_list">List</string>
|
||||||
<string name="text_loading_3d">Loading…</string>
|
<string name="text_loading_3d">Loading…</string>
|
||||||
<string name="text_manual_vocabulary_list">Manual vocabulary list</string>
|
<string name="text_manual_vocabulary_list">Manual vocabulary list</string>
|
||||||
<string name="text_match_the_pairs">Match the pairs</string>
|
|
||||||
<string name="text_mismatch_between_question_ids_in_exercise_and_questions_found_in_repository">Mismatch between question IDs in exercise and questions found in repository.</string>
|
<string name="text_mismatch_between_question_ids_in_exercise_and_questions_found_in_repository">Mismatch between question IDs in exercise and questions found in repository.</string>
|
||||||
<string name="text_mistral">Mistral</string>
|
<string name="text_mistral">Mistral</string>
|
||||||
<string name="text_more_options">More options</string>
|
<string name="text_more_options">More options</string>
|
||||||
@@ -846,7 +822,6 @@
|
|||||||
<string name="text_no_items_available">No items available</string>
|
<string name="text_no_items_available">No items available</string>
|
||||||
<string name="text_no_key">No Key</string>
|
<string name="text_no_key">No Key</string>
|
||||||
<string name="text_no_models_found">No models found</string>
|
<string name="text_no_models_found">No models found</string>
|
||||||
<string name="text_no_progress">No progress</string>
|
|
||||||
<string name="text_no_valid_api_configuration_could_be_found">No valid API configuration could be found in the settings. Before using this app, you have to configure at least one API provider.</string>
|
<string name="text_no_valid_api_configuration_could_be_found">No valid API configuration could be found in the settings. Before using this app, you have to configure at least one API provider.</string>
|
||||||
<string name="text_no_vocabulary_available">No vocabulary available.</string>
|
<string name="text_no_vocabulary_available">No vocabulary available.</string>
|
||||||
<string name="text_no_vocabulary_due_today">No Vocabulary Due Today</string>
|
<string name="text_no_vocabulary_due_today">No Vocabulary Due Today</string>
|
||||||
@@ -891,8 +866,6 @@
|
|||||||
<string name="text_select_translations_to_add">Select Translations to Add</string>
|
<string name="text_select_translations_to_add">Select Translations to Add</string>
|
||||||
<string name="text_selected">Selected</string>
|
<string name="text_selected">Selected</string>
|
||||||
<string name="text_sentenc_version_information_not_available">Version information not available.</string>
|
<string name="text_sentenc_version_information_not_available">Version information not available.</string>
|
||||||
<string name="text_sentence_oops_something_went_wrong">Oops! Something went wrong.</string>
|
|
||||||
<string name="text_sentence_this_is_an_info_message">This is an info message.</string>
|
|
||||||
<string name="text_show_error_message">Show Error Message</string>
|
<string name="text_show_error_message">Show Error Message</string>
|
||||||
<string name="text_show_info_message">Show Info Message</string>
|
<string name="text_show_info_message">Show Info Message</string>
|
||||||
<string name="text_show_loading">Show Loading</string>
|
<string name="text_show_loading">Show Loading</string>
|
||||||
@@ -902,12 +875,9 @@
|
|||||||
<string name="text_shuffle_languages">Shuffle Languages</string>
|
<string name="text_shuffle_languages">Shuffle Languages</string>
|
||||||
<string name="text_shuffle_languages_description">Shuffle what language comes first. Does not affect language direction preferences.</string>
|
<string name="text_shuffle_languages_description">Shuffle what language comes first. Does not affect language direction preferences.</string>
|
||||||
<string name="text_shuffle_languages_disabled_by_direction">Disable language direction preference to enable shuffling.</string>
|
<string name="text_shuffle_languages_disabled_by_direction">Disable language direction preference to enable shuffling.</string>
|
||||||
<string name="text_shuffle_questions">Shuffle questions</string>
|
|
||||||
<string name="text_some_items_are_in_the_wrong_category">Some items are in the wrong category.</string>
|
<string name="text_some_items_are_in_the_wrong_category">Some items are in the wrong category.</string>
|
||||||
<string name="text_stage_2d">Stage %1$s</string>
|
<string name="text_stage_2d">Stage %1$s</string>
|
||||||
<string name="text_start_over">Start Over</string>
|
<string name="text_start_over">Start Over</string>
|
||||||
<string name="text_success_em">Success!</string>
|
|
||||||
<string name="text_swap_sides">Swap sides</string>
|
|
||||||
<string name="text_text">Text</string>
|
<string name="text_text">Text</string>
|
||||||
<string name="text_that_s_not_quite_right">That\'s not quite right.</string>
|
<string name="text_that_s_not_quite_right">That\'s not quite right.</string>
|
||||||
<string name="text_the_correct_answer_is_2d">The correct answer is:</string>
|
<string name="text_the_correct_answer_is_2d">The correct answer is:</string>
|
||||||
@@ -934,13 +904,10 @@
|
|||||||
<string name="text_very_frequent">Very Frequent</string>
|
<string name="text_very_frequent">Very Frequent</string>
|
||||||
<string name="text_view_all">View All</string>
|
<string name="text_view_all">View All</string>
|
||||||
<string name="text_visit_my_website">Visit my website</string>
|
<string name="text_visit_my_website">Visit my website</string>
|
||||||
<string name="text_vocab_empty">No Vocabulary Items could be found. Add now?</string>
|
|
||||||
<string name="text_vocabulary_prompt">Vocabulary Prompt</string>
|
<string name="text_vocabulary_prompt">Vocabulary Prompt</string>
|
||||||
<string name="text_watch_video_again">Watch Video Again</string>
|
<string name="text_watch_video_again">Watch Video Again</string>
|
||||||
<string name="text_widget_title_weekly_activity">Weekly Activity</string>
|
<string name="text_widget_title_weekly_activity">Weekly Activity</string>
|
||||||
<string name="text_word_of_the_day">Word of the Day</string>
|
<string name="text_word_of_the_day">Word of the Day</string>
|
||||||
<string name="text_word_pair_exercise">Word Pair Exercise</string>
|
|
||||||
<string name="text_word_pair_settings">Word Pair Settings</string>
|
|
||||||
<string name="text_your_own_ai">Your Own AI</string>
|
<string name="text_your_own_ai">Your Own AI</string>
|
||||||
<string name="text_youtube_link">YouTube Link</string>
|
<string name="text_youtube_link">YouTube Link</string>
|
||||||
|
|
||||||
@@ -949,16 +916,13 @@
|
|||||||
<string name="the_server_could_not_understand_the_request">The server could not understand the request.</string>
|
<string name="the_server_could_not_understand_the_request">The server could not understand the request.</string>
|
||||||
<string name="the_server_understood_the_request_but_is_refusing_to_authorize_it">The server understood the request, but is refusing to authorize it.</string>
|
<string name="the_server_understood_the_request_but_is_refusing_to_authorize_it">The server understood the request, but is refusing to authorize it.</string>
|
||||||
|
|
||||||
<string name="this_is_a_hint">This is a hint.</string>
|
|
||||||
<string name="this_is_a_sample_output_text">This is a sample output text.</string>
|
<string name="this_is_a_sample_output_text">This is a sample output text.</string>
|
||||||
<string name="this_is_the_content_inside_the_card">This is the content inside the card.</string>
|
<string name="this_is_the_content_inside_the_card">This is the content inside the card.</string>
|
||||||
<string name="this_is_the_main_content">This is the main content.</string>
|
|
||||||
<string name="this_mode_will_not_affect_your_progress_in_stages">This mode will not affect your progress in stages.</string>
|
<string name="this_mode_will_not_affect_your_progress_in_stages">This mode will not affect your progress in stages.</string>
|
||||||
|
|
||||||
<string name="timeout">Timeout</string>
|
<string name="timeout">Timeout</string>
|
||||||
|
|
||||||
<string name="title_corrector">Corrector</string>
|
<string name="title_corrector">Corrector</string>
|
||||||
<string name="title_dashboard">Dashboard</string>
|
|
||||||
<string name="title_developer_options">Developer Options</string>
|
<string name="title_developer_options">Developer Options</string>
|
||||||
<string name="title_http_status_codes">HTTP Status Codes</string>
|
<string name="title_http_status_codes">HTTP Status Codes</string>
|
||||||
<string name="title_items_without_grammar">Items Without Grammar</string>
|
<string name="title_items_without_grammar">Items Without Grammar</string>
|
||||||
@@ -966,7 +930,6 @@
|
|||||||
<string name="title_settings">Settings</string>
|
<string name="title_settings">Settings</string>
|
||||||
<string name="title_show_success_message">Show Success Message</string>
|
<string name="title_show_success_message">Show Success Message</string>
|
||||||
<string name="title_single">Single</string>
|
<string name="title_single">Single</string>
|
||||||
<string name="title_title_preview_title">Preview Title</string>
|
|
||||||
<string name="title_widget_due_today">Due Today</string>
|
<string name="title_widget_due_today">Due Today</string>
|
||||||
<string name="title_widget_streak">Streak</string>
|
<string name="title_widget_streak">Streak</string>
|
||||||
|
|
||||||
@@ -990,7 +953,7 @@
|
|||||||
|
|
||||||
<string name="vocabulary_added_successfully">Vocabulary Added</string>
|
<string name="vocabulary_added_successfully">Vocabulary Added</string>
|
||||||
<string name="vocabulary_repository">Vocabulary Repository</string>
|
<string name="vocabulary_repository">Vocabulary Repository</string>
|
||||||
<string name="vocabulary_settings">Progress Settings</string>
|
<string name="label_vocabulary_settings">Progress Settings</string>
|
||||||
|
|
||||||
<string name="website_url">Website URL</string>
|
<string name="website_url">Website URL</string>
|
||||||
|
|
||||||
@@ -1043,7 +1006,6 @@
|
|||||||
<string name="hint_scan_hint_title">Finding the right AI model</string>
|
<string name="hint_scan_hint_title">Finding the right AI model</string>
|
||||||
<string name="hint_translate_how_it_works">How translation works</string>
|
<string name="hint_translate_how_it_works">How translation works</string>
|
||||||
<string name="label_no_category">None</string>
|
<string name="label_no_category">None</string>
|
||||||
<string name="text_select">Select</string>
|
|
||||||
<string name="text_search">Search</string>
|
<string name="text_search">Search</string>
|
||||||
<string name="text_language_settings_description">Set what languages you want to use in the app. Languages that are not activated will not appear in this app. You can also add your own language to the list, or change an existing language (region/locale)</string>
|
<string name="text_language_settings_description">Set what languages you want to use in the app. Languages that are not activated will not appear in this app. You can also add your own language to the list, or change an existing language (region/locale)</string>
|
||||||
|
|
||||||
@@ -1120,7 +1082,6 @@
|
|||||||
<string name="label_stats">Stats</string>
|
<string name="label_stats">Stats</string>
|
||||||
<string name="label_library">Library</string>
|
<string name="label_library">Library</string>
|
||||||
<string name="label_edit">Edit</string>
|
<string name="label_edit">Edit</string>
|
||||||
<string name="label_total_wordss">Total Words</string>
|
|
||||||
<string name="label_new_words">New Words</string>
|
<string name="label_new_words">New Words</string>
|
||||||
<string name="desc_expand_your_vocabulary">Expand your vocabulary</string>
|
<string name="desc_expand_your_vocabulary">Expand your vocabulary</string>
|
||||||
<string name="label_settings">Settings</string>
|
<string name="label_settings">Settings</string>
|
||||||
@@ -1131,11 +1092,16 @@
|
|||||||
<string name="label_see_history">See History</string>
|
<string name="label_see_history">See History</string>
|
||||||
<string name="label_weekly_progress">Weekly Progress</string>
|
<string name="label_weekly_progress">Weekly Progress</string>
|
||||||
<string name="cd_go">Go</string>
|
<string name="cd_go">Go</string>
|
||||||
<string name="label_aapply_filters">Apply Filters</string>
|
|
||||||
<string name="label_sort_by">Sort By</string>
|
<string name="label_sort_by">Sort By</string>
|
||||||
<string name="label_reset">Reset</string>
|
<string name="label_reset">Reset</string>
|
||||||
<string name="label_filter_cards">Filter Cards</string>
|
<string name="label_filter_cards">Filter Cards</string>
|
||||||
<string name="text_desc_organize_vocabulary_groups">Organize Your Vocabulary in Groups</string>
|
<string name="text_desc_organize_vocabulary_groups">Organize Your Vocabulary in Groups</string>
|
||||||
<string name="text_add_new_word_to_list">Extract a New Word to Your List</string>
|
<string name="text_add_new_word_to_list">Extract a New Word to Your List</string>
|
||||||
<string name="cd_scroll_to_top">Scroll to top</string>
|
<string name="cd_scroll_to_top">Scroll to top</string>
|
||||||
|
<string name="cd_settings">Settings</string>
|
||||||
|
<string name="label_import_csv">Import CSV</string>
|
||||||
|
<string name="label_ai_generator">AI Generator</string>
|
||||||
|
<string name="label_new_wordss">New Words</string>
|
||||||
|
<string name="label_recently_added">Recently Added</string>
|
||||||
|
<string name="label_view_all">View All</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,511 +0,0 @@
|
|||||||
@file:Suppress("HardCodedStringLiteral")
|
|
||||||
|
|
||||||
package eu.gaudian.translator.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.gaudian.translator.model.Language
|
|
||||||
import eu.gaudian.translator.model.communication.ApiManager
|
|
||||||
import eu.gaudian.translator.model.communication.ModelType
|
|
||||||
import eu.gaudian.translator.utils.dictionary.DictionaryService
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import junit.framework.TestCase.assertEquals
|
|
||||||
import junit.framework.TestCase.assertFalse
|
|
||||||
import junit.framework.TestCase.assertNotNull
|
|
||||||
import junit.framework.TestCase.assertTrue
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class JsonHelperTest {
|
|
||||||
|
|
||||||
private lateinit var jsonHelper: JsonHelper
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
jsonHelper = JsonHelper()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseJson should successfully parse valid JSON`() = runTest {
|
|
||||||
// Given
|
|
||||||
val validJson = """{"word": "hello", "parts": [{"title": "Definition", "content": "A greeting"}]}"""
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This test would need proper serializer setup
|
|
||||||
// For now, just test the validation
|
|
||||||
assertTrue(jsonHelper.validateRequiredFields(validJson, listOf("word", "parts")))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `validateRequiredFields should return false for missing fields`() {
|
|
||||||
// Given
|
|
||||||
val incompleteJson = """{"word": "hello"}"""
|
|
||||||
val requiredFields = listOf("word", "parts")
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(incompleteJson, requiredFields)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertFalse(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `validateRequiredFields should return true for complete fields`() {
|
|
||||||
// Given
|
|
||||||
val completeJson = """{"word": "hello", "parts": []}"""
|
|
||||||
val requiredFields = listOf("word", "parts")
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(completeJson, requiredFields)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `extractField should return correct value`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"word": "hello", "parts": []}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.extractField(json, "word")
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals("hello", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `extractField should return null for missing field`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"word": "hello"}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.extractField(json, "missing")
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals(null, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApiRequestHandlerTest {
|
|
||||||
|
|
||||||
private lateinit var apiManager: ApiManager
|
|
||||||
private lateinit var context: Context
|
|
||||||
private lateinit var apiRequestHandler: ApiRequestHandler
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
apiManager = mockk(relaxed = true)
|
|
||||||
context = mockk(relaxed = true)
|
|
||||||
apiRequestHandler = ApiRequestHandler(apiManager, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `executeRequest with template should handle successful response`() = runTest {
|
|
||||||
// Given
|
|
||||||
val template = DictionaryDefinitionRequest(
|
|
||||||
word = "test",
|
|
||||||
language = "English",
|
|
||||||
requestedParts = "Definition"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mock the API manager response
|
|
||||||
every {
|
|
||||||
apiManager.getCompletion(
|
|
||||||
prompt = any(),
|
|
||||||
callback = any(),
|
|
||||||
modelType = ModelType.DICTIONARY
|
|
||||||
)
|
|
||||||
} answers {
|
|
||||||
val callback = thirdArg<(String?) -> Unit>()
|
|
||||||
callback("""{"word": "test", "parts": [{"title": "Definition", "content": "A test"}]}""")
|
|
||||||
}
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = apiRequestHandler.executeRequest(template)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
result.getOrNull()?.let { response ->
|
|
||||||
assertEquals("test", response.word)
|
|
||||||
assertEquals(1, response.parts.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `executeRequest with template should handle API failure`() = runTest {
|
|
||||||
// Given
|
|
||||||
val template = DictionaryDefinitionRequest(
|
|
||||||
word = "test",
|
|
||||||
language = "English",
|
|
||||||
requestedParts = "Definition"
|
|
||||||
)
|
|
||||||
|
|
||||||
every {
|
|
||||||
apiManager.getCompletion(
|
|
||||||
prompt = any(),
|
|
||||||
callback = any(),
|
|
||||||
modelType = ModelType.DICTIONARY
|
|
||||||
)
|
|
||||||
} answers {
|
|
||||||
val callback = secondArg<(String) -> Unit>()
|
|
||||||
callback("API Error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = apiRequestHandler.executeRequest(template)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result.isFailure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApiRequestTemplatesTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `DictionaryDefinitionRequest should build correct prompt`() {
|
|
||||||
// Given
|
|
||||||
val template = DictionaryDefinitionRequest(
|
|
||||||
word = "hello",
|
|
||||||
language = "English",
|
|
||||||
requestedParts = "Definition, Origin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// When
|
|
||||||
val prompt = template.buildPrompt()
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(prompt.contains("hello"))
|
|
||||||
assertTrue(prompt.contains("English"))
|
|
||||||
assertTrue(prompt.contains("Definition, Origin"))
|
|
||||||
assertTrue(prompt.contains("JSON object"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `VocabularyTranslationRequest should build correct prompt`() {
|
|
||||||
// Given
|
|
||||||
val words = listOf("hello", "world")
|
|
||||||
val template = VocabularyTranslationRequest(
|
|
||||||
words = words,
|
|
||||||
languageFirst = "English",
|
|
||||||
languageSecond = "Spanish"
|
|
||||||
)
|
|
||||||
|
|
||||||
// When
|
|
||||||
val prompt = template.buildPrompt()
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(prompt.contains("English"))
|
|
||||||
assertTrue(prompt.contains("Spanish"))
|
|
||||||
assertTrue(prompt.contains("hello"))
|
|
||||||
assertTrue(prompt.contains("world"))
|
|
||||||
assertTrue(prompt.contains("flashcards"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `TextCorrectionRequest should build correct prompt`() {
|
|
||||||
// Given
|
|
||||||
val template = TextCorrectionRequest(
|
|
||||||
textToCorrect = "Helo world",
|
|
||||||
language = "English",
|
|
||||||
grammarOnly = true,
|
|
||||||
tone = null
|
|
||||||
)
|
|
||||||
|
|
||||||
// When
|
|
||||||
val prompt = template.buildPrompt()
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(prompt.contains("Helo world"))
|
|
||||||
assertTrue(prompt.contains("English"))
|
|
||||||
assertTrue(prompt.contains("grammar, spelling, and punctuation"))
|
|
||||||
assertTrue(prompt.contains("correctedText"))
|
|
||||||
assertTrue(prompt.contains("explanation"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `SynonymGenerationRequest should build correct prompt`() {
|
|
||||||
// Given
|
|
||||||
val template = SynonymGenerationRequest(
|
|
||||||
amount = 5,
|
|
||||||
language = "English",
|
|
||||||
term = "happy",
|
|
||||||
translation = "feliz",
|
|
||||||
translationLanguage = "Spanish",
|
|
||||||
languageCode = "en"
|
|
||||||
)
|
|
||||||
|
|
||||||
// When
|
|
||||||
val prompt = template.buildPrompt()
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(prompt.contains("happy"))
|
|
||||||
assertTrue(prompt.contains("feliz"))
|
|
||||||
assertTrue(prompt.contains("English"))
|
|
||||||
assertTrue(prompt.contains("Spanish"))
|
|
||||||
assertTrue(prompt.contains("synonyms"))
|
|
||||||
assertTrue(prompt.contains("proximity"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DictionaryServiceTest {
|
|
||||||
|
|
||||||
private lateinit var context: Context
|
|
||||||
private lateinit var dictionaryService: DictionaryService
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
context = mockk(relaxed = true)
|
|
||||||
dictionaryService = DictionaryService(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `searchDefinition should handle successful response`() = runTest {
|
|
||||||
// This test would require mocking the ApiRequestHandler
|
|
||||||
// For now, just verify the method exists and basic structure
|
|
||||||
val language = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This would need proper mocking setup
|
|
||||||
assertNotNull(language)
|
|
||||||
assertEquals("English", language.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getExampleSentence should handle successful response`() = runTest {
|
|
||||||
val languageFirst = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
val languageSecond = Language(
|
|
||||||
name = "Spanish",
|
|
||||||
nameResId = 2,
|
|
||||||
code = "es",
|
|
||||||
englishName = "Spanish",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
assertNotNull(languageFirst)
|
|
||||||
assertNotNull(languageSecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VocabularyServiceTest {
|
|
||||||
|
|
||||||
private lateinit var context: Context
|
|
||||||
private lateinit var vocabularyService: VocabularyService
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
context = mockk(relaxed = true)
|
|
||||||
vocabularyService = VocabularyService(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `translateWordsBatch should handle empty list`() = runTest {
|
|
||||||
// Given
|
|
||||||
val emptyWords = emptyList<String>()
|
|
||||||
val languageFirst = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
val languageSecond = Language(
|
|
||||||
name = "Spanish",
|
|
||||||
nameResId = 2,
|
|
||||||
code = "es",
|
|
||||||
englishName = "Spanish",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = vocabularyService.translateWordsBatch(emptyWords, languageFirst, languageSecond)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
assertEquals(0, result.getOrNull()?.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `translateWordsBatch should filter blank words`() = runTest {
|
|
||||||
// Given
|
|
||||||
val wordsWithBlanks = listOf("hello", "", "world", " ")
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This would need proper mocking setup for actual API calls
|
|
||||||
assertEquals(2, wordsWithBlanks.filter { it.isNotBlank() }.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `generateSynonyms should use correct parameters`() = runTest {
|
|
||||||
// Given
|
|
||||||
val language = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
val translationLanguage = Language(
|
|
||||||
name = "Spanish",
|
|
||||||
nameResId = 2,
|
|
||||||
code = "es",
|
|
||||||
englishName = "Spanish",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This would need proper mocking setup for actual API calls
|
|
||||||
assertNotNull(language)
|
|
||||||
assertNotNull(translationLanguage)
|
|
||||||
assertEquals("English", language.englishName)
|
|
||||||
assertEquals("Spanish", translationLanguage.englishName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TranslationServiceTest {
|
|
||||||
|
|
||||||
private lateinit var context: Context
|
|
||||||
private lateinit var translationService: TranslationService
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
context = mockk(relaxed = true)
|
|
||||||
translationService = TranslationService(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `simpleTranslateTo should handle basic translation`() = runTest {
|
|
||||||
// Given
|
|
||||||
val targetLanguage = Language(
|
|
||||||
name = "Spanish",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "es",
|
|
||||||
englishName = "Spanish",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This would need proper mocking setup for actual API calls
|
|
||||||
assertNotNull(targetLanguage)
|
|
||||||
assertEquals("Spanish", targetLanguage.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `translateSentence should handle sentence translation`() = runTest {
|
|
||||||
// Given
|
|
||||||
val sentence = "Hello world"
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This would need proper mocking setup for actual API calls
|
|
||||||
assertNotNull(sentence)
|
|
||||||
assertEquals("Hello world", sentence)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CorrectionServiceTest {
|
|
||||||
|
|
||||||
private lateinit var context: Context
|
|
||||||
private lateinit var correctionService: CorrectionService
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
context = mockk(relaxed = true)
|
|
||||||
correctionService = CorrectionService(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `correctText should handle basic correction`() = runTest {
|
|
||||||
// Given
|
|
||||||
val language = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This would need proper mocking setup for actual API calls
|
|
||||||
assertNotNull(language)
|
|
||||||
assertEquals("English", language.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `correctText should handle grammar only mode`() = runTest {
|
|
||||||
// Given
|
|
||||||
val textToCorrect = "Helo world"
|
|
||||||
val language = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This would need proper mocking setup for actual API calls
|
|
||||||
assertNotNull(textToCorrect)
|
|
||||||
assertNotNull(language)
|
|
||||||
assertEquals("Helo world", textToCorrect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Integration test example
|
|
||||||
class ApiArchitectureIntegrationTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `end to end dictionary lookup should work`() = runTest {
|
|
||||||
// This would be an integration test that tests the full flow
|
|
||||||
// from service -> template -> API handler -> JSON parsing
|
|
||||||
|
|
||||||
// Given
|
|
||||||
val mockContext = mockk<Context>(relaxed = true)
|
|
||||||
val mockApiManager = mockk<ApiManager>(relaxed = true)
|
|
||||||
|
|
||||||
// Setup mock API response
|
|
||||||
every {
|
|
||||||
mockApiManager.getCompletion(
|
|
||||||
prompt = any(),
|
|
||||||
callback = any(),
|
|
||||||
modelType = ModelType.DICTIONARY
|
|
||||||
)
|
|
||||||
} answers {
|
|
||||||
val callback = thirdArg<(String?) -> Unit>()
|
|
||||||
callback("""{"word": "test", "parts": [{"title": "Definition", "content": "A test word"}]}""")
|
|
||||||
}
|
|
||||||
|
|
||||||
// When
|
|
||||||
val apiHandler = ApiRequestHandler(mockApiManager, mockContext)
|
|
||||||
val template = DictionaryDefinitionRequest(
|
|
||||||
word = "test",
|
|
||||||
language = "English",
|
|
||||||
requestedParts = "Definition"
|
|
||||||
)
|
|
||||||
val result = apiHandler.executeRequest(template)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
result.getOrNull()?.let { response ->
|
|
||||||
assertEquals("test", response.word)
|
|
||||||
assertEquals(1, response.parts.size)
|
|
||||||
assertEquals("Definition", response.parts[0].title)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package eu.gaudian.translator.utils
|
|
||||||
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.test.TestDispatcher
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
||||||
import org.junit.rules.TestWatcher
|
|
||||||
import org.junit.runner.Description
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test rule for setting up coroutine testing environment
|
|
||||||
*/
|
|
||||||
class CoroutineTestRule @OptIn(ExperimentalCoroutinesApi::class) constructor(
|
|
||||||
val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
|
|
||||||
) : TestWatcher() {
|
|
||||||
|
|
||||||
lateinit var testScope: TestScope
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun starting(description: Description) {
|
|
||||||
super.starting(description)
|
|
||||||
testScope = TestScope(testDispatcher)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base test class for API architecture tests
|
|
||||||
*/
|
|
||||||
abstract class BaseApiTest {
|
|
||||||
|
|
||||||
@get:org.junit.Rule
|
|
||||||
val coroutineRule = CoroutineTestRule()
|
|
||||||
|
|
||||||
protected val testDispatcher = coroutineRule.testDispatcher
|
|
||||||
protected val testScope = coroutineRule.testScope
|
|
||||||
}
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
@file:Suppress("HardCodedStringLiteral")
|
|
||||||
|
|
||||||
package eu.gaudian.translator.utils
|
|
||||||
|
|
||||||
import junit.framework.TestCase.assertEquals
|
|
||||||
import junit.framework.TestCase.assertFalse
|
|
||||||
import junit.framework.TestCase.assertNotNull
|
|
||||||
import junit.framework.TestCase.assertTrue
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class JsonHelperComprehensiveTest {
|
|
||||||
|
|
||||||
private lateinit var jsonHelper: JsonHelper
|
|
||||||
private val jsonParser = Json { ignoreUnknownKeys = true; isLenient = true }
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
jsonHelper = JsonHelper()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseJson should handle valid DictionaryApiResponse`() {
|
|
||||||
// Given
|
|
||||||
val validJson = """{"word": "hello", "parts": [{"title": "Definition", "content": "A greeting"}]}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(validJson, listOf("word", "parts"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseJson should handle valid VocabularyApiResponse`() {
|
|
||||||
// Given
|
|
||||||
val validJson = """{"flashcards": [{"front": {"language": "English", "word": "hello"}, "back": {"language": "Spanish", "word": "hola"}}]}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(validJson, listOf("flashcards"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseJson should handle valid TranslationApiResponse`() {
|
|
||||||
// Given
|
|
||||||
val validJson = """{"translatedText": "hola"}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(validJson, listOf("translatedText"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseJson should handle valid CorrectionResponse`() {
|
|
||||||
// Given
|
|
||||||
val validJson = """{"correctedText": "Hello world", "explanation": "Capitalized first letter"}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(validJson, listOf("correctedText", "explanation"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseJson should handle valid EtymologyApiResponse`() {
|
|
||||||
// Given
|
|
||||||
val validJson = """{"word": "hello", "timeline": [{"year": "1890", "language": "Old English", "description": "From greeting"}], "relatedWords": []}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(validJson, listOf("word", "timeline", "relatedWords"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseJson should handle valid SynonymApiResponse`() {
|
|
||||||
// Given
|
|
||||||
val validJson = """{"synonyms": [{"word": "hi", "proximity": 95}, {"word": "greetings", "proximity": 85}]}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(validJson, listOf("synonyms"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `validateRequiredFields should return false for empty JSON`() {
|
|
||||||
// Given
|
|
||||||
val emptyJson = """{}"""
|
|
||||||
val requiredFields = listOf("word")
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(emptyJson, requiredFields)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertFalse(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `validateRequiredFields should return false for malformed JSON`() {
|
|
||||||
// Given
|
|
||||||
val malformedJson = """{"word": "hello", "parts": ["""
|
|
||||||
val requiredFields = listOf("word", "parts")
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(malformedJson, requiredFields)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertFalse(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `validateRequiredFields should handle nested objects`() {
|
|
||||||
// Given
|
|
||||||
val nestedJson = """{"flashcards": [{"front": {"language": "English", "word": "hello"}]}"""
|
|
||||||
val requiredFields = listOf("flashcards")
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(nestedJson, requiredFields)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `extractField should extract simple field`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"word": "hello", "parts": []}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.extractField(json, "word")
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals("hello", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `extractField should extract nested field`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"flashcards": [{"front": {"language": "English", "word": "hello"}}]}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.extractField(json, "flashcards")
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertNotNull(result)
|
|
||||||
assertTrue(result!!.contains("English"))
|
|
||||||
assertTrue(result.contains("hello"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `extractField should return null for non-existent field`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"word": "hello"}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.extractField(json, "nonexistent")
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals(null, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `extractField should handle array fields`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"synonyms": [{"word": "hi", "proximity": 95}]}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.extractField(json, "synonyms")
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertNotNull(result)
|
|
||||||
assertTrue(result!!.contains("hi"))
|
|
||||||
assertTrue(result.contains("95"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `formatForDisplay should format simple JSON`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"word": "hello", "parts": []}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.formatForDisplay(json)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result.contains("{\n"))
|
|
||||||
assertTrue(result.contains("}\n"))
|
|
||||||
assertTrue(result.contains(",\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `formatForDisplay should handle malformed JSON gracefully`() {
|
|
||||||
// Given
|
|
||||||
val malformedJson = """{"word": "hello", "parts": ["""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.formatForDisplay(malformedJson)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals(malformedJson, result) // Should return original if formatting fails
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `cleanAndValidateJson should handle markdown wrapped JSON`() {
|
|
||||||
// Given
|
|
||||||
val markdownJson = """
|
|
||||||
```json
|
|
||||||
{"word": "hello", "parts": []}
|
|
||||||
```
|
|
||||||
""".trim()
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(markdownJson, listOf("word", "parts"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `cleanAndValidateJson should handle JSON with comments`() {
|
|
||||||
// Given
|
|
||||||
val jsonWithComments = """
|
|
||||||
{
|
|
||||||
"word": "hello", // This is the word
|
|
||||||
"parts": [] /* This is the parts array */
|
|
||||||
}
|
|
||||||
""".trim()
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(jsonWithComments, listOf("word", "parts"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `cleanAndValidateJson should handle JSON with trailing commas`() {
|
|
||||||
// Given
|
|
||||||
val jsonWithTrailingComma = """
|
|
||||||
{
|
|
||||||
"word": "hello",
|
|
||||||
"parts": [],
|
|
||||||
}
|
|
||||||
""".trim()
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonHelper.validateRequiredFields(jsonWithTrailingComma, listOf("word", "parts"))
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test data classes
|
|
||||||
@Serializable
|
|
||||||
data class TestDictionaryResponse(
|
|
||||||
val word: String,
|
|
||||||
val parts: List<TestEntryPart>
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class TestEntryPart(
|
|
||||||
val title: String,
|
|
||||||
val content: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class TestVocabularyResponse(
|
|
||||||
val flashcards: List<TestFlashcard>
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class TestFlashcard(
|
|
||||||
val front: TestCardSide,
|
|
||||||
val back: TestCardSide
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class TestCardSide(
|
|
||||||
val language: String,
|
|
||||||
val word: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `real parsing test with DictionaryApiResponse`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"word": "hello", "parts": [{"title": "Definition", "content": "A greeting"}]}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonParser.decodeFromString(TestDictionaryResponse.serializer(), json)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals("hello", result.word)
|
|
||||||
assertEquals(1, result.parts.size)
|
|
||||||
assertEquals("Definition", result.parts[0].title)
|
|
||||||
assertEquals("A greeting", result.parts[0].content)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `real parsing test with VocabularyApiResponse`() {
|
|
||||||
// Given
|
|
||||||
val json = """{"flashcards": [{"front": {"language": "English", "word": "hello"}, "back": {"language": "Spanish", "word": "hola"}}]}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = jsonParser.decodeFromString(TestVocabularyResponse.serializer(), json)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals(1, result.flashcards.size)
|
|
||||||
assertEquals("English", result.flashcards[0].front.language)
|
|
||||||
assertEquals("hello", result.flashcards[0].front.word)
|
|
||||||
assertEquals("Spanish", result.flashcards[0].back.language)
|
|
||||||
assertEquals("hola", result.flashcards[0].back.word)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
@file:Suppress("HardCodedStringLiteral")
|
|
||||||
|
|
||||||
package eu.gaudian.translator.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.gaudian.translator.model.Language
|
|
||||||
import eu.gaudian.translator.model.VocabularyItem
|
|
||||||
import io.mockk.mockk
|
|
||||||
import junit.framework.TestCase.assertEquals
|
|
||||||
import junit.framework.TestCase.assertNotNull
|
|
||||||
import junit.framework.TestCase.assertTrue
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class ServiceFixesTest {
|
|
||||||
|
|
||||||
private lateinit var context: Context
|
|
||||||
private lateinit var vocabularyService: VocabularyService
|
|
||||||
private lateinit var exerciseService: ExerciseService
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
context = mockk(relaxed = true)
|
|
||||||
vocabularyService = VocabularyService(context)
|
|
||||||
exerciseService = ExerciseService(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `VocabularyService generateSynonyms should have correct return type`() = runTest {
|
|
||||||
// Given
|
|
||||||
val language = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
val translationLanguage = Language(
|
|
||||||
name = "Spanish",
|
|
||||||
nameResId = 2,
|
|
||||||
code = "es",
|
|
||||||
englishName = "Spanish",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This test verifies the method signature is correct
|
|
||||||
// The actual API call would need mocking for full testing
|
|
||||||
assertNotNull(language)
|
|
||||||
assertNotNull(translationLanguage)
|
|
||||||
assertEquals("English", language.englishName)
|
|
||||||
assertEquals("Spanish", translationLanguage.englishName)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `ExerciseService should use JsonHelper instead of VocabularyParser`() = runTest {
|
|
||||||
// Given
|
|
||||||
val exerciseTitle = "Test Exercise"
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This test verifies the ExerciseService can be instantiated without errors
|
|
||||||
assertNotNull(exerciseService)
|
|
||||||
assertNotNull(exerciseTitle)
|
|
||||||
assertEquals("Test Exercise", exerciseTitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseVocabularyFromJson should handle simple JSON`() {
|
|
||||||
// Given
|
|
||||||
val jsonResponse = """{"hello": "hola", "world": "mundo"}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = parseVocabularyFromJson(jsonResponse)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals(2, result.size)
|
|
||||||
assertEquals("hello", result[0].wordFirst)
|
|
||||||
assertEquals("hola", result[0].wordSecond)
|
|
||||||
assertEquals("world", result[1].wordFirst)
|
|
||||||
assertEquals("mundo", result[1].wordSecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseVocabularyFromJson should handle empty JSON`() {
|
|
||||||
// Given
|
|
||||||
val jsonResponse = """{}"""
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = parseVocabularyFromJson(jsonResponse)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals(0, result.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parseVocabularyFromJson should handle malformed JSON`() {
|
|
||||||
// Given
|
|
||||||
val malformedJson = """{"hello": "hola", "world": """
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = parseVocabularyFromJson(malformedJson)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertEquals(0, result.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to test the private parseVocabularyFromJson method
|
|
||||||
*/
|
|
||||||
private fun parseVocabularyFromJson(jsonResponse: String): List<VocabularyItem> {
|
|
||||||
return try {
|
|
||||||
// Clean and validate the JSON first
|
|
||||||
val jsonHelper = JsonHelper()
|
|
||||||
val cleanedJson = jsonHelper.cleanAndValidateJson(jsonResponse)
|
|
||||||
|
|
||||||
// Parse the JSON object for vocabulary items
|
|
||||||
val jsonObject = kotlinx.serialization.json.Json.parseToJsonElement(cleanedJson).jsonObject
|
|
||||||
|
|
||||||
val vocabularyItems = mutableListOf<VocabularyItem>()
|
|
||||||
var id = 1
|
|
||||||
|
|
||||||
for ((wordFirst, wordSecondElement) in jsonObject) {
|
|
||||||
val wordSecond = wordSecondElement.jsonPrimitive.content.trim()
|
|
||||||
val vocabularyItem = VocabularyItem(
|
|
||||||
id = id++,
|
|
||||||
languageFirstId = -1, // Will be set by caller
|
|
||||||
languageSecondId = -1, // Will be set by caller
|
|
||||||
wordFirst = wordFirst.trim(),
|
|
||||||
wordSecond = wordSecond
|
|
||||||
)
|
|
||||||
vocabularyItems.add(vocabularyItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
vocabularyItems
|
|
||||||
} catch (e: Exception) {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `VocabularyService translateWordsBatch should handle empty list`() = runTest {
|
|
||||||
// Given
|
|
||||||
val emptyWords = emptyList<String>()
|
|
||||||
val languageFirst = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
val languageSecond = Language(
|
|
||||||
name = "Spanish",
|
|
||||||
nameResId = 2,
|
|
||||||
code = "es",
|
|
||||||
englishName = "Spanish",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// When
|
|
||||||
val result = vocabularyService.translateWordsBatch(emptyWords, languageFirst, languageSecond)
|
|
||||||
|
|
||||||
// Then
|
|
||||||
assertTrue(result.isSuccess)
|
|
||||||
assertEquals(0, result.getOrNull()?.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `VocabularyService generateVocabularyItems should handle basic parameters`() = runTest {
|
|
||||||
// Given
|
|
||||||
val category = "Basic"
|
|
||||||
val languageFirst = Language(
|
|
||||||
name = "English",
|
|
||||||
nameResId = 1,
|
|
||||||
code = "en",
|
|
||||||
englishName = "English",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
val languageSecond = Language(
|
|
||||||
name = "Spanish",
|
|
||||||
nameResId = 2,
|
|
||||||
code = "es",
|
|
||||||
englishName = "Spanish",
|
|
||||||
region = ""
|
|
||||||
)
|
|
||||||
val amount = 5
|
|
||||||
|
|
||||||
// When & Then
|
|
||||||
// This test verifies the method exists and accepts parameters correctly
|
|
||||||
assertNotNull(category)
|
|
||||||
assertNotNull(languageFirst)
|
|
||||||
assertNotNull(languageSecond)
|
|
||||||
assertEquals("Basic", category)
|
|
||||||
assertEquals("English", languageFirst.name)
|
|
||||||
assertEquals("Spanish", languageSecond.name)
|
|
||||||
assertEquals(5, amount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "9.0.0"
|
agp = "9.0.1"
|
||||||
annotation = "1.9.1"
|
annotation = "1.9.1"
|
||||||
converterGson = "3.0.0"
|
converterGson = "3.0.0"
|
||||||
core = "13.0.0"
|
core = "13.0.0"
|
||||||
coreSplashscreen = "1.2.0"
|
coreSplashscreen = "1.2.0"
|
||||||
coreTesting = "2.2.0"
|
coreTesting = "2.2.0"
|
||||||
datastorePreferences = "1.2.0"
|
datastorePreferences = "1.2.0"
|
||||||
foundation = "1.10.2"
|
foundation = "1.10.3"
|
||||||
hiltAndroidTesting = "2.59.1"
|
hiltAndroidTesting = "2.59.1"
|
||||||
jsoup = "1.22.1"
|
jsoup = "1.22.1"
|
||||||
kotlin = "2.3.10"
|
kotlin = "2.3.10"
|
||||||
@@ -21,14 +21,14 @@ kotlinxCoroutinesTest = "1.10.2"
|
|||||||
kotlinxDatetime = "0.7.1"
|
kotlinxDatetime = "0.7.1"
|
||||||
kotlinxSerializationJson = "1.10.0"
|
kotlinxSerializationJson = "1.10.0"
|
||||||
lifecycleRuntimeKtx = "2.10.0"
|
lifecycleRuntimeKtx = "2.10.0"
|
||||||
activityCompose = "1.12.3"
|
activityCompose = "1.12.4"
|
||||||
composeBom = "2026.01.01"
|
composeBom = "2026.02.00"
|
||||||
loggingInterceptor = "5.3.2"
|
loggingInterceptor = "5.3.2"
|
||||||
materialIconsExtended = "1.7.8"
|
materialIconsExtended = "1.7.8"
|
||||||
mockitoCore = "5.21.0"
|
mockitoCore = "5.21.0"
|
||||||
mockitoKotlin = "6.2.3"
|
mockitoKotlin = "6.2.3"
|
||||||
navigationCompose = "2.9.7"
|
navigationCompose = "2.9.7"
|
||||||
pagingRuntimeKtx = "3.4.0"
|
pagingRuntimeKtx = "3.4.1"
|
||||||
reorderable = "0.9.6"
|
reorderable = "0.9.6"
|
||||||
retrofit = "3.0.0"
|
retrofit = "3.0.0"
|
||||||
material = "1.13.0"
|
material = "1.13.0"
|
||||||
@@ -36,14 +36,12 @@ material3 = "1.4.0"
|
|||||||
runner = "1.7.0"
|
runner = "1.7.0"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
navigationTesting = "2.9.7"
|
navigationTesting = "2.9.7"
|
||||||
foundationLayout = "1.10.2"
|
foundationLayout = "1.10.3"
|
||||||
room = "2.8.4"
|
room = "2.8.4"
|
||||||
coreKtxVersion = "1.7.0"
|
coreKtxVersion = "1.7.0"
|
||||||
truth = "1.4.5"
|
truth = "1.4.5"
|
||||||
zstdJni = "1.5.7-7"
|
zstdJni = "1.5.7-7"
|
||||||
composeMarkdown = "0.5.8"
|
composeMarkdown = "0.5.8"
|
||||||
jitpack = "1.0.10"
|
|
||||||
foundationVersion = "1.10.3"
|
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -104,10 +102,8 @@ hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", ve
|
|||||||
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.3.0" }
|
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.3.0" }
|
||||||
mockk = { module = "io.mockk:mockk", version = "1.14.9" }
|
mockk = { module = "io.mockk:mockk", version = "1.14.9" }
|
||||||
compose-markdown = { module = "com.github.jeziellago:compose-markdown", version.ref = "composeMarkdown" }
|
compose-markdown = { module = "com.github.jeziellago:compose-markdown", version.ref = "composeMarkdown" }
|
||||||
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundationVersion" }
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
hilt-android = { id = "com.google.dagger.hilt.android", version = "2.59.1" }
|
hilt-android = { id = "com.google.dagger.hilt.android", version = "2.59.1" }
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|||||||
Reference in New Issue
Block a user