Migrate ViewModels to Hilt dependency injection and refactor ViewModel instantiation across the app

This commit is contained in:
jonasgaudian
2026-02-13 14:05:41 +01:00
parent 6f661bb743
commit e5c58f58f6
61 changed files with 618 additions and 691 deletions

6
.idea/misc.xml generated
View File

@@ -1,4 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="kotlinx.serialization.Serializable" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />

View File

@@ -1,3 +1,5 @@
@file:Suppress("unused", "HardCodedStringLiteral")
package eu.gaudian.translator.di
import android.app.Application
@@ -11,10 +13,18 @@ import eu.gaudian.translator.model.repository.ApiRepository
import eu.gaudian.translator.model.repository.DictionaryFileRepository
import eu.gaudian.translator.model.repository.DictionaryLookupRepository
import eu.gaudian.translator.model.repository.DictionaryRepository
import eu.gaudian.translator.model.repository.ExerciseRepository
import eu.gaudian.translator.model.repository.LanguageConfigRepository
import eu.gaudian.translator.model.repository.LanguageRepository
import eu.gaudian.translator.model.repository.SettingsRepository
import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.CorrectionService
import eu.gaudian.translator.utils.ExerciseService
import eu.gaudian.translator.utils.StatusMessageService
import eu.gaudian.translator.utils.TranslationService
import eu.gaudian.translator.utils.VocabularyService
import eu.gaudian.translator.utils.YouTubeApiService
import eu.gaudian.translator.utils.YouTubeExerciseService
import eu.gaudian.translator.utils.dictionary.DictionaryService
import javax.inject.Singleton
@@ -58,6 +68,12 @@ object RepositoryModule {
return LanguageRepository(application)
}
@Provides
@Singleton
fun provideLanguageConfigRepository(application: Application): LanguageConfigRepository {
return LanguageConfigRepository(application)
}
@Provides
@Singleton
fun provideDictionaryFileRepository(application: Application): DictionaryFileRepository {
@@ -87,4 +103,46 @@ object RepositoryModule {
fun provideStatusMessageService(): StatusMessageService {
return StatusMessageService
}
@Provides
@Singleton
fun provideVocabularyService(application: Application): VocabularyService {
return VocabularyService(application)
}
@Provides
@Singleton
fun provideExerciseRepository(application: Application): ExerciseRepository {
return ExerciseRepository(application)
}
@Provides
@Singleton
fun provideExerciseService(application: Application): ExerciseService {
return ExerciseService(application)
}
@Provides
@Singleton
fun provideTranslationService(application: Application): TranslationService {
return TranslationService(application)
}
@Provides
@Singleton
fun provideCorrectionService(application: Application): CorrectionService {
return CorrectionService(application)
}
@Provides
@Singleton
fun provideYouTubeApiService(): YouTubeApiService {
return YouTubeApiService
}
@Provides
@Singleton
fun provideYouTubeExerciseService(): YouTubeExerciseService {
return YouTubeExerciseService
}
}

View File

@@ -0,0 +1,85 @@
@file:Suppress("HardCodedStringLiteral")
package eu.gaudian.translator.model.repository
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.gaudian.translator.model.grammar.LanguageConfig
import eu.gaudian.translator.utils.Log
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.json.Json
import javax.inject.Inject
import javax.inject.Singleton
/**
* Repository for managing language configurations loaded from assets.
* This is a singleton that can be injected into multiple ViewModels.
*/
@Singleton
class LanguageConfigRepository @Inject constructor(
@param:ApplicationContext private val context: Context
) {
private val _configs = MutableStateFlow<Map<String, LanguageConfig>>(emptyMap())
val configs: StateFlow<Map<String, LanguageConfig>> = _configs.asStateFlow()
private val jsonParser = Json { ignoreUnknownKeys = true }
init {
loadAllConfigs()
}
/**
* Loads all language configuration files from the "language_configs" asset directory.
*/
private fun loadAllConfigs() {
try {
val configFiles = context.assets.list("language_configs") ?: return
val loadedConfigs = mutableMapOf<String, LanguageConfig>()
for (fileName in configFiles) {
if (fileName.endsWith(".json")) {
val jsonString = context.assets.open("language_configs/$fileName")
.bufferedReader()
.use { it.readText() }
val config = jsonParser.decodeFromString<LanguageConfig>(jsonString)
loadedConfigs[config.language_code] = config
}
}
_configs.value = loadedConfigs
} catch (e: Exception) {
Log.e("Failed to load language configs: ${e.message}")
e.printStackTrace()
}
}
/**
* Retrieves the configuration for a specific language code.
*
* @param langCode The ISO language code (e.g., "de").
* @return The LanguageConfig for the given code, or null if not found.
*/
fun getConfigForLanguage(langCode: String): LanguageConfig? {
Log.d("Fetching config for language: $langCode")
return _configs.value[langCode]
}
/**
* Retrieves the set of articles for a specific language code.
*
* @param langCode The ISO language code (e.g., "de").
* @return A Set<String> of articles for the given language. Returns an empty set if
* the language code is not found or if the language has no articles defined.
*/
fun getArticlesForLanguage(langCode: String): Set<String> {
return try {
val config = _configs.value[langCode]
config?.articles?.toSet() ?: emptySet()
} catch (e: Exception) {
Log.e("Error retrieving articles for '$langCode': ${e.message}")
emptySet()
}
}
}

View File

@@ -45,12 +45,77 @@ object StatusMessageService {
}
}
@Suppress("unused")
fun showSimpleMessage(text: String, type: MessageDisplayType = MessageDisplayType.INFO) {
scope.launch {
_actions.emit(StatusAction.ShowMessage(text, type, 5))
}
}
fun showErrorMessage(text: String, timeoutInSeconds: Int = 5) {
scope.launch {
_actions.emit(StatusAction.ShowMessage(
text, MessageDisplayType.ERROR,
timeoutInSeconds = timeoutInSeconds
)
)
}
}
fun showLoadingMessage(text: String, timeoutInSeconds: Int = 0) {
scope.launch {
_actions.emit(StatusAction.ShowMessage(
text, MessageDisplayType.LOADING,
timeoutInSeconds = timeoutInSeconds
))
}
}
fun showInfoMessage(text: String, timeoutInSeconds: Int = 3) {
scope.launch {
_actions.emit(StatusAction.ShowMessage(
text, MessageDisplayType.INFO,
timeoutInSeconds = timeoutInSeconds
))
}
}
fun showSuccessMessage(text: String, timeoutInSeconds: Int = 3) {
scope.launch {
_actions.emit(StatusAction.ShowMessage(
text, MessageDisplayType.SUCCESS,
timeoutInSeconds = timeoutInSeconds
))
}
}
fun showPermanentMessage(text: String, type: MessageDisplayType) {
scope.launch {
_actions.emit(StatusAction.ShowPermanentMessage(text, type))
}
}
fun cancelPermanentMessage() {
scope.launch {
_actions.emit(StatusAction.CancelPermanentMessage)
}
}
fun hideMessageBar() {
scope.launch {
_actions.emit(StatusAction.HideMessageBar)
}
}
fun cancelAllMessages() {
scope.launch {
_actions.emit(StatusAction.CancelAllMessages)
}
}
fun showActionableMessage(text: String, type: MessageDisplayType, action: MessageAction) {
scope.launch {
_actions.emit(StatusAction.ShowActionableMessage(text, type, action))
}
}
}

View File

@@ -2,7 +2,6 @@
package eu.gaudian.translator.utils
import android.app.Application
import android.content.Context
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.VocabularyItem
@@ -10,9 +9,9 @@ import eu.gaudian.translator.model.communication.ApiManager
import eu.gaudian.translator.model.grammar.GrammaticalFeature
import eu.gaudian.translator.model.grammar.LanguageConfig
import eu.gaudian.translator.model.grammar.VocabularyFeatures
import eu.gaudian.translator.model.repository.LanguageConfigRepository
import eu.gaudian.translator.model.repository.SettingsRepository
import eu.gaudian.translator.utils.StringHelper.isSentenceStrict
import eu.gaudian.translator.viewmodel.LanguageConfigViewModel
import eu.gaudian.translator.viewmodel.MessageDisplayType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -28,8 +27,7 @@ class VocabularyService(context: Context) {
private val apiRequestHandler = ApiRequestHandler(ApiManager(context), context)
private val settingsRepository = SettingsRepository(context)
private val languageConfigViewModel =
LanguageConfigViewModel(context.applicationContext as Application)
private val languageConfigRepository = LanguageConfigRepository(context)
private val jsonParser = Json { ignoreUnknownKeys = true; isLenient = true }
suspend fun fetchZipfFrequency(word: String, languageCode: String): Result<Float> = withContext(Dispatchers.IO) {
@@ -269,7 +267,7 @@ class VocabularyService(context: Context) {
val wordsWithCategoryOnly = mutableListOf<ClassifiedWord>()
classifiedWords.forEach { word ->
val config = languageConfigViewModel.getConfigForLanguage(word.languageCode)
val config = languageConfigRepository.getConfigForLanguage(word.languageCode)
val categoryHasFields = config?.categories?.get(word.category)?.fields?.isNotEmpty() ?: false
if (categoryHasFields) {
wordsWithFeaturesToFetch.add(word)
@@ -283,7 +281,7 @@ class VocabularyService(context: Context) {
val featureResults = groupedByLangAndCategory.map { (groupKey, words) ->
async {
val (langCode, category) = groupKey
val languageConfig = languageConfigViewModel.getConfigForLanguage(langCode)
val languageConfig = languageConfigRepository.getConfigForLanguage(langCode)
fetchFeaturesForGroup(words, languageConfig, category)
}
}.awaitAll().filterNotNull().flatten()
@@ -329,7 +327,7 @@ class VocabularyService(context: Context) {
val wordsToClassify = wordsNeedingApi.map {
WordToAnalyze(it.tempId, it.word, languages[it.languageId]?.englishName ?: "")
}
val languageConfigs = languageConfigViewModel.configs.value
val languageConfigs = languageConfigRepository.configs.value
val possibleCategories = languageConfigs.values.flatMap { it.categories.keys }.distinct()
val wordsToClassifyJson = jsonParser.encodeToString(wordsToClassify)
@@ -442,7 +440,7 @@ class VocabularyService(context: Context) {
)
var finalWord = classifiedWord.word
val languageConfig = languageConfigViewModel.getConfigForLanguage(classifiedWord.languageCode)
val languageConfig = languageConfigRepository.getConfigForLanguage(classifiedWord.languageCode)
val categoryConfig = languageConfig?.categories?.get(newGrammaticalFeature.category)
if (!isSentenceStrict(finalWord)) {

View File

@@ -2,7 +2,6 @@
package eu.gaudian.translator.view
import android.app.Application
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
@@ -12,9 +11,7 @@ import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavType
@@ -29,6 +26,7 @@ import eu.gaudian.translator.view.dictionary.EtymologyResultScreen
import eu.gaudian.translator.view.dictionary.MainDictionaryScreen
import eu.gaudian.translator.view.exercises.ExerciseSessionScreen
import eu.gaudian.translator.view.exercises.MainExerciseScreen
import eu.gaudian.translator.view.exercises.YouTubeBrowserScreen
import eu.gaudian.translator.view.exercises.YouTubeExerciseScreen
import eu.gaudian.translator.view.settings.DictionaryOptionsScreen
import eu.gaudian.translator.view.settings.SettingsRoutes
@@ -46,12 +44,7 @@ import eu.gaudian.translator.view.vocabulary.VocabularyExerciseHostScreen
import eu.gaudian.translator.view.vocabulary.VocabularyHeatmapScreen
import eu.gaudian.translator.view.vocabulary.VocabularyListScreen
import eu.gaudian.translator.view.vocabulary.VocabularySortingScreen
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.ExerciseViewModel
import eu.gaudian.translator.viewmodel.ProgressViewModel
import okhttp3.internal.platform.PlatformRegistry.applicationContext
private const val ANIMATION_DURATION = 200
private const val TRANSITION_DURATION = 300
@Composable
@@ -59,8 +52,8 @@ fun AppNavHost(
navController: NavHostController,
modifier: Modifier = Modifier
) {
val exerciseViewModel: ExerciseViewModel = viewModel()
val progressViewModel: ProgressViewModel = ProgressViewModel.getInstance(applicationContext as Application)
// 1. Define your Main Tab "Leaf" Routes (the actual start destinations of your graphs)
val mainTabRoutes = setOf(
@@ -134,8 +127,8 @@ fun AppNavHost(
// Define all other navigation graphs at the same top level.
translationGraph(navController)
dictionaryGraph(navController)
vocabularyGraph(navController, progressViewModel)
exerciseGraph(navController, exerciseViewModel)
vocabularyGraph(navController)
exerciseGraph(navController)
settingsGraph(navController)
}
}
@@ -190,10 +183,8 @@ fun NavGraphBuilder.dictionaryGraph(navController: NavHostController) {
}
}
@OptIn(androidx.compose.animation.ExperimentalAnimationApi::class)
fun NavGraphBuilder.vocabularyGraph(
navController: NavHostController,
progressViewModel: ProgressViewModel
) {
navigation(
startDestination = "main_vocabulary",
@@ -246,14 +237,12 @@ fun NavGraphBuilder.vocabularyGraph(
}
composable("language_progress") {
LanguageProgressScreen(
wordsLearned = progressViewModel.totalWordsCompleted.collectAsState().value,
navController = navController
)
}
composable("vocabulary_heatmap") {
VocabularyHeatmapScreen(
viewModel = progressViewModel,
navController = navController,
)
}
@@ -358,8 +347,6 @@ fun NavGraphBuilder.vocabularyGraph(
}
composable("category_list_screen") {
CategoryListScreen(
categoryViewModel = CategoryViewModel.getInstance(applicationContext as Application),
progressViewModel = ProgressViewModel.getInstance(applicationContext as Application),
onNavigateBack = { navController.popBackStack() },
onCategoryClicked = { categoryId ->
navController.navigate("category_detail/$categoryId")
@@ -392,7 +379,6 @@ fun NavGraphBuilder.vocabularyGraph(
@OptIn(androidx.compose.animation.ExperimentalAnimationApi::class)
fun NavGraphBuilder.exerciseGraph(
navController: NavHostController,
exerciseViewModel: ExerciseViewModel
) {
navigation(
startDestination = "main_exercise",
@@ -401,25 +387,21 @@ fun NavGraphBuilder.exerciseGraph(
composable("main_exercise") {
MainExerciseScreen(
navController = navController,
exerciseViewModel = exerciseViewModel
)
}
composable("exercise_session") {
ExerciseSessionScreen(
navController = navController,
exerciseViewModel = exerciseViewModel
)
}
composable("youtube_exercise") {
YouTubeExerciseScreen(
navController = navController,
exerciseViewModel = exerciseViewModel
navController = navController
)
}
composable("youtube_browse") {
eu.gaudian.translator.view.exercises.YouTubeBrowserScreen(
YouTubeBrowserScreen(
navController = navController,
exerciseViewModel = exerciseViewModel
)
}
}

View File

@@ -38,7 +38,6 @@ 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 androidx.lifecycle.viewmodel.compose.viewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.TagCategory
@@ -57,10 +56,10 @@ enum class DialogCategoryType { TAG, FILTER }
@Composable
fun AddCategoryDialog(
onDismiss: () -> Unit,
categoryViewModel: CategoryViewModel
onDismiss: () -> Unit
) {
val activity = LocalContext.current.findActivity()
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
var categoryName by remember { mutableStateOf("") }
var selectedLanguages by remember { mutableStateOf<List<Language>>(emptyList()) }
@@ -160,7 +159,6 @@ fun AddCategoryDialog(
)
} else { // Dictionary
DictionarySelectionContent(
categoryViewModel = categoryViewModel,
selectedPair = selectedDictionaryPair,
onPairSelected = { selectedDictionaryPair = it }
)
@@ -251,11 +249,11 @@ fun AddCategoryDialog(
@Composable
private fun DictionarySelectionContent(
categoryViewModel: CategoryViewModel,
selectedPair: Pair<Int, Int>?,
onPairSelected: (Pair<Int, Int>) -> Unit
) {
val activity = LocalContext.current.findActivity()
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
var allDictionaries by remember { mutableStateOf<List<Pair<Language, Language>>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) }
@@ -334,8 +332,7 @@ private fun DictionarySelectionContent(
@Composable
fun AddCategoryDialogPreview() {
AddCategoryDialog(
onDismiss = {},
categoryViewModel = viewModel()
onDismiss = {}
)
}
@@ -344,7 +341,6 @@ fun AddCategoryDialogPreview() {
fun DictionarySelectionContentPreview() {
LocalContext.current
DictionarySelectionContent(
categoryViewModel = viewModel(),
selectedPair = Pair(R.string.language_1, R.string.language_2),
onPairSelected = {}
)

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.dialogs
import android.app.Application
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -39,7 +38,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.VocabularyItem
import eu.gaudian.translator.ui.theme.ThemePreviews
@@ -51,32 +49,29 @@ import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.StatusViewModel
import eu.gaudian.translator.viewmodel.TranslationViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.internal.platform.PlatformRegistry.applicationContext
enum class VocabularyDialogTab { SINGLE, MULTIPLE, TEXT }
@Composable
fun AddVocabularyDialog(
statusViewModel: StatusViewModel,
languageViewModel: LanguageViewModel,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
initialWord: String? = null,
translation: String? = null,
onDismissRequest: () -> Unit,
showMultiple: Boolean = true
) {
LocalContext.current
val activity = LocalContext.current.findActivity()
val statusViewModel: StatusViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel = hiltViewModel<VocabularyViewModel>(viewModelStoreOwner = activity)
val coroutineScope = rememberCoroutineScope()
val translationViewModel: TranslationViewModel = viewModel()
val categoryViewModel: CategoryViewModel = viewModel()
val translationViewModel: TranslationViewModel = hiltViewModel(viewModelStoreOwner = activity)
val connectionConfigured = LocalConnectionConfigured.current
@@ -235,6 +230,7 @@ fun AddVocabularyDialog(
val isGenerating by vocabularyViewModel.isGenerating.collectAsState()
val generated by vocabularyViewModel.generatedVocabularyItems.collectAsState()
LaunchedEffect(isGenerating, generated) {
@Suppress("ControlFlowWithEmptyBody")
if (!isGenerating && showReview) {
//TODO think if we still need this
}
@@ -341,14 +337,12 @@ fun AddVocabularyDialog(
) {
androidx.compose.material3.Surface(modifier = Modifier.fillMaxSize()) {
VocabularyReviewScreen(
vocabularyViewModel = vocabularyViewModel,
onConfirm = { selectedItems, categoryIds ->
vocabularyViewModel.addVocabularyItems(selectedItems, categoryIds)
showReview = false
onDismissRequest()
},
onCancel = { showReview = false },
categoryViewModel = categoryViewModel
onCancel = { showReview = false }
)
}
}
@@ -358,14 +352,7 @@ fun AddVocabularyDialog(
@ThemePreviews
@Composable
fun AddVocabularyDialogPreview() {
val activity = LocalContext.current.findActivity()
val statusViewModel: StatusViewModel = viewModel()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as Application)
AddVocabularyDialog(
statusViewModel = statusViewModel,
languageViewModel = languageViewModel,
vocabularyViewModel = vocabularyViewModel,
onDismissRequest = {})
}

View File

@@ -20,14 +20,16 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.TagCategory
import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCheckbox
import eu.gaudian.translator.view.composable.AppDropdownMenuItem
@@ -39,7 +41,6 @@ import eu.gaudian.translator.viewmodel.CategoryViewModel
@Composable
fun CategoryDropdown(
categoryViewModel: CategoryViewModel,
initialCategoryId: Int? = null,
onCategorySelected: (List<VocabularyCategory?>) -> Unit,
noneSelectable: Boolean? = true,
@@ -47,6 +48,8 @@ fun CategoryDropdown(
onlyLists: Boolean = false,
addCategory: Boolean = false
) {
val activity = LocalContext.current.findActivity()
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
var expanded by remember { mutableStateOf(false) }
val categories by categoryViewModel.categories.collectAsState(initial = emptyList())
val selectableCategories = if (onlyLists) categories.filterIsInstance<TagCategory>() else categories
@@ -229,7 +232,6 @@ fun CategoryDropdown(
@Composable
fun CategoryDropdownPreview() {
CategoryDropdown(
categoryViewModel = viewModel(),
onCategorySelected = {}
)
}

View File

@@ -18,11 +18,9 @@ import eu.gaudian.translator.R
import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.DialogButton
import eu.gaudian.translator.viewmodel.CategoryViewModel
@Composable
fun CategorySelectionDialog(
viewModel: CategoryViewModel,
onCategorySelected: (List<VocabularyCategory?>) -> Unit,
onDismissRequest: () -> Unit,
) {
@@ -35,7 +33,6 @@ fun CategorySelectionDialog(
CategoryDropdown(
categoryViewModel = viewModel,
onCategorySelected = { categories ->
selectedCategory = categories
},

View File

@@ -1,23 +1,26 @@
package eu.gaudian.translator.view.dialogs
import android.app.Application
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppAlertDialog
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import okhttp3.internal.platform.PlatformRegistry.applicationContext
@Composable
fun DeleteItemsDialog(
viewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
onDismiss: () -> Unit,
categoryId: Int,
) {
val categoryVocabularyItemDelete = viewModel.categoryVocabularyItemDelete
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val categoryVocabularyItemDelete = vocabularyViewModel.categoryVocabularyItemDelete
@Suppress("HardCodedStringLiteral")
Log.d("DeleteItemsDialog", "categoryVocabularyItemDelete: $categoryVocabularyItemDelete")
@@ -32,7 +35,7 @@ fun DeleteItemsDialog(
confirmButton = {
TextButton(
onClick = {
viewModel.deleteVocabularyItemsByCategory(categoryId)
vocabularyViewModel.deleteVocabularyItemsByCategory(categoryId)
onDismiss()
}
) {

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.dialogs
import android.app.Application
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -25,15 +24,19 @@ 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppSlider
import eu.gaudian.translator.view.composable.AppTextField
@@ -41,17 +44,14 @@ import eu.gaudian.translator.view.composable.DialogButton
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
import eu.gaudian.translator.view.hints.getImportVocabularyHint
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import kotlinx.coroutines.launch
import okhttp3.internal.platform.PlatformRegistry.applicationContext
@Composable
fun ImportVocabularyDialog(
onDismiss: () -> Unit,
languageViewModel: LanguageViewModel,
categoryViewModel: CategoryViewModel,
vocabularyViewModel : VocabularyViewModel,
optionalDescription: String? = null,
optionalSearchTerm: String? = null
@@ -64,7 +64,6 @@ fun ImportVocabularyDialog(
navController = navController,
onDismiss = onDismiss,
languageViewModel = languageViewModel,
vocabularyViewModel = vocabularyViewModel,
optionalDescription = optionalDescription,
optionalSearchTerm = optionalSearchTerm
)
@@ -78,13 +77,11 @@ fun ImportVocabularyDialog(
// Full-screen surface to ensure the dialog covers content and stays above the main FAB/menu
Surface(modifier = Modifier.fillMaxSize()) {
VocabularyReviewScreen(
vocabularyViewModel = vocabularyViewModel,
onConfirm = { selectedItems, categoryIds ->
vocabularyViewModel.addVocabularyItems(selectedItems, categoryIds)
onDismiss()
},
onCancel = onDismiss,
categoryViewModel = categoryViewModel
onCancel = onDismiss
)
}
}
@@ -97,10 +94,11 @@ fun ImportDialogContent(
navController: NavController,
onDismiss: () -> Unit,
languageViewModel: LanguageViewModel,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
optionalDescription: String? = null,
optionalSearchTerm: String? = null
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
var category by remember { mutableStateOf(optionalSearchTerm ?: "") }
var amount by remember { mutableFloatStateOf(1f) }
val coroutineScope = rememberCoroutineScope()
@@ -206,3 +204,19 @@ fun ImportDialogContent(
}
)
}
@Suppress("HardCodedStringLiteral")
@Preview
@Composable
fun ImportDialogContentPreview() {
val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
ImportDialogContent(
navController = rememberNavController(),
onDismiss = {},
languageViewModel = languageViewModel,
optionalDescription = "Let AI find vocabulary for you",
optionalSearchTerm = "Travel"
)
}

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.dialogs
import android.app.Application
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -28,16 +27,12 @@ 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
import okhttp3.internal.platform.PlatformRegistry.applicationContext
@Composable
fun StartExerciseDialog(
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
categoryViewModel: CategoryViewModel = CategoryViewModel.getInstance(applicationContext as Application),
onDismiss: () -> Unit,
onConfirm: (
categories: List<VocabularyCategory>,
@@ -47,6 +42,7 @@ fun StartExerciseDialog(
) {
val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel : VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val coroutineScope = rememberCoroutineScope()
var lids by remember { mutableStateOf<List<Int>>(emptyList()) }
var languages by remember { mutableStateOf<List<Language>>(emptyList()) }
@@ -85,7 +81,6 @@ fun StartExerciseDialog(
languages
)
CategoryDropdown(
categoryViewModel = categoryViewModel,
onCategorySelected = { categories ->
selectedCategories = categories.filterIsInstance<VocabularyCategory>()
},

View File

@@ -11,26 +11,21 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppFabMenu
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.FabMenuItem
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.StatusViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
@Composable
fun VocabularyMenu(
modifier: Modifier = Modifier,
statusViewModel: StatusViewModel = viewModel(),
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application),
categoryViewModel: CategoryViewModel = viewModel(),
) {
val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
var showAddVocabularyDialog by remember { mutableStateOf(false) }
var showImportVocabularyDialog by remember { mutableStateOf(false) }
var showAddCategoryDialog by remember { mutableStateOf(false) }
@@ -57,9 +52,6 @@ fun VocabularyMenu(
if (showAddVocabularyDialog) {
AddVocabularyDialog(
statusViewModel = statusViewModel,
languageViewModel = languageViewModel,
vocabularyViewModel = vocabularyViewModel,
onDismissRequest = { showAddVocabularyDialog = false }
)
}
@@ -67,7 +59,6 @@ fun VocabularyMenu(
if (showImportVocabularyDialog) {
ImportVocabularyDialog(
languageViewModel = languageViewModel,
categoryViewModel = categoryViewModel,
vocabularyViewModel = vocabularyViewModel,
onDismiss = { showImportVocabularyDialog = false }
)
@@ -75,7 +66,6 @@ fun VocabularyMenu(
if (showAddCategoryDialog) {
AddCategoryDialog(
categoryViewModel = categoryViewModel,
onDismiss = { showAddCategoryDialog = false }
)
}

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.dialogs
import android.app.Application
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -26,25 +25,25 @@ 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.VocabularyCategory
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.AppCheckbox
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.hints.getVocabularyReviewHint
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import okhttp3.internal.platform.PlatformRegistry.applicationContext
@Composable
fun VocabularyReviewScreen(
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
categoryViewModel: CategoryViewModel,
onConfirm: (List<VocabularyItem>, List<Int>) -> Unit,
onCancel: () -> Unit
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel : VocabularyViewModel = hiltViewModel(activity)
val generatedItems: List<VocabularyItem> by vocabularyViewModel.generatedVocabularyItems.collectAsState()
val selectedItems = remember { mutableStateListOf<VocabularyItem>() }
val duplicates = remember { mutableStateListOf<Boolean>() }
@@ -129,7 +128,6 @@ fun VocabularyReviewScreen(
modifier = Modifier.padding(8.dp)
)
CategoryDropdown(
categoryViewModel = categoryViewModel,
onCategorySelected = { categories: List<VocabularyCategory?> ->
selectedCategoryId = categories.filterNotNull().map { it.id }
},

View File

@@ -31,7 +31,6 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.DictionaryEntry
@@ -50,8 +49,6 @@ import eu.gaudian.translator.view.dialogs.AddVocabularyDialog
import eu.gaudian.translator.viewmodel.DictionaryViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.SettingsViewModel
import eu.gaudian.translator.viewmodel.StatusViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import kotlinx.coroutines.launch
enum class DictionaryResultTab(
@@ -76,8 +73,7 @@ fun DictionaryResultScreen(
val activity = LocalContext.current.findActivity()
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel: VocabularyViewModel = viewModel(viewModelStoreOwner = activity)
val statusViewModel: StatusViewModel = viewModel()
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
// State Collection
@@ -214,9 +210,6 @@ fun DictionaryResultScreen(
if (showAddVocabularyDialog) {
entryData?.let {
AddVocabularyDialog(
statusViewModel = statusViewModel,
languageViewModel = languageViewModel,
vocabularyViewModel = vocabularyViewModel,
initialWord = it.entry.word,
onDismissRequest = {
@Suppress("AssignedValueIsNeverRead")

View File

@@ -1,9 +1,9 @@
package eu.gaudian.translator.view.dictionary
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.utils.findActivity
@@ -14,10 +14,13 @@ import eu.gaudian.translator.viewmodel.LanguageViewModel
fun DictionaryScreen(
navController: NavController,
onEntryClick: (eu.gaudian.translator.model.DictionaryEntry) -> Unit = {},
dictionaryViewModel: DictionaryViewModel,
languageViewModel: LanguageViewModel,
onNavigateToOptions: () -> Unit
) {
val activity = LocalContext.current.findActivity()
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
// Use the new refactored component
DictionaryScreenContent(
navController = navController,
@@ -31,15 +34,10 @@ fun DictionaryScreen(
@Preview
@Composable
fun DictionaryScreenPreview() {
val activity = androidx.compose.ui.platform.LocalContext.current.findActivity()
val dictionaryViewModel: DictionaryViewModel = viewModel()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
DictionaryScreen(
navController = rememberNavController(),
onEntryClick = {},
dictionaryViewModel = dictionaryViewModel,
languageViewModel = languageViewModel,
onNavigateToOptions = {}
)
}

View File

@@ -23,7 +23,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.R
@@ -98,7 +97,7 @@ fun EtymologyScreen(
@Composable
fun EtymologyScreenPreview() {
val activity = LocalContext.current.findActivity()
val dictionaryViewModel: DictionaryViewModel = viewModel()
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
EtymologyScreen(
navController = rememberNavController(),

View File

@@ -58,7 +58,7 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.grammar.DictionaryEntryData
@@ -71,6 +71,7 @@ import eu.gaudian.translator.model.grammar.UnifiedMorphology
import eu.gaudian.translator.model.grammar.UnifiedMorphologyParser
import eu.gaudian.translator.utils.dictionary.LocalDictionaryWordInfo
import eu.gaudian.translator.utils.dictionary.PartOfSpeechTranslator
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AutoResizeSingleLineText
import eu.gaudian.translator.viewmodel.DictionaryViewModel
@@ -92,7 +93,8 @@ fun RichLocalEntryDisplay(
expandable: Boolean = true, // Enables whole-entry collapsing
initiallyExpanded: Boolean = true // Default state
) {
val languageConfigViewModel: LanguageConfigViewModel = viewModel()
val activity = LocalContext.current.findActivity()
val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(activity)
val allConfigs by languageConfigViewModel.configs.collectAsState()
val languageConfig = allConfigs[langCode]

View File

@@ -2,7 +2,6 @@
package eu.gaudian.translator.view.dictionary
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -11,11 +10,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.R
@@ -46,17 +42,12 @@ private data class DictionaryTab(override val title: String, override val icon:
fun MainDictionaryScreen(
navController: NavController
) {
val viewModelStoreOwner = if (LocalInspectionMode.current) {
null
} else {
LocalActivity.current
}
val activity = LocalContext.current.findActivity()
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val correctionViewModel: CorrectionViewModel = viewModel(viewModelStoreOwner = viewModelStoreOwner as ViewModelStoreOwner)
val correctionViewModel: CorrectionViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val dictionaryTabs = getDictionaryTabs()
@@ -78,8 +69,6 @@ fun MainDictionaryScreen(
when (selectedTab) {
dictionaryTabs[0] -> DictionaryScreen(
navController = navController,
dictionaryViewModel = dictionaryViewModel,
languageViewModel = languageViewModel,
onEntryClick = { entry ->
// Set flag indicating navigation is from external source (not DictionaryResultScreen)
dictionaryViewModel.setNavigatingFromDictionaryResult(false)

View File

@@ -22,11 +22,13 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
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.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Exercise
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons
@@ -40,7 +42,8 @@ fun ExerciseListScreen(
onLongExerciseClicked: (Exercise) -> Unit,
onDeleteClicked: (Exercise) -> Unit,
) {
val exerciseViewModel: ExerciseViewModel = viewModel()
val activity = LocalContext.current.findActivity()
val exerciseViewModel: ExerciseViewModel = hiltViewModel(activity)
val exercises by exerciseViewModel.exercises.collectAsState()
AppOutlinedCard{

View File

@@ -33,11 +33,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.R
@@ -52,6 +53,7 @@ import eu.gaudian.translator.model.VocabularyTestQuestion
import eu.gaudian.translator.model.WordOrderQuestion
import eu.gaudian.translator.ui.theme.ThemePreviews
import eu.gaudian.translator.ui.theme.semanticColors
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.ComponentDefaults
@@ -65,9 +67,11 @@ import eu.gaudian.translator.viewmodel.VocabularyViewModel
@Composable
fun ExerciseSessionScreen(
navController: NavController,
exerciseViewModel: ExerciseViewModel, // Changed: No longer creates its own instance
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application)
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val exerciseViewModel: ExerciseViewModel = hiltViewModel(viewModelStoreOwner = activity)
val sessionState by exerciseViewModel.exerciseSessionState.collectAsState()
var showVocabulary by remember { mutableStateOf(true) }
@@ -267,7 +271,9 @@ fun ExerciseQuestionScreen(
onCloseClick: () -> Unit,
navController: NavController
) {
val exerciseViewModel = viewModel<ExerciseViewModel>()
val activity = LocalContext.current.findActivity()
val exerciseViewModel = hiltViewModel<ExerciseViewModel>(viewModelStoreOwner = activity)
Scaffold(
topBar = {

View File

@@ -24,11 +24,14 @@ 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.painterResource
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Exercise
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppAlertDialog
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppIcons
@@ -52,13 +55,13 @@ enum class ExerciseTab(override val title: String, override val icon: ImageVecto
* showing a tabbed layout for the dashboard and the list of all exercises.
*
* @param navController The main NavController to handle navigation to an exercise session.
* @param exerciseViewModel The ViewModel for managing exercise data and state.
*/
@Composable
fun MainExerciseScreen(
navController: NavController,
exerciseViewModel: ExerciseViewModel // Changed: No longer creates its own instance
) {
val activity = LocalContext.current.findActivity()
val exerciseViewModel : ExerciseViewModel = hiltViewModel(activity)
val aiState by exerciseViewModel.aiGenerationState.collectAsState()
var exerciseToDelete by remember { mutableStateOf<Exercise?>(null) }
var showGenerateDialog by remember { mutableStateOf(false) }

View File

@@ -29,8 +29,10 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar
@@ -45,9 +47,11 @@ import java.net.URLDecoder
@Composable
@SuppressLint("SetJavaScriptEnabled")
fun YouTubeBrowserScreen(
navController: NavController,
exerciseViewModel: ExerciseViewModel
navController: NavController
) {
val activity = LocalContext.current.findActivity()
val exerciseViewModel : ExerciseViewModel = hiltViewModel(activity)
var pendingVideoUrl by remember { mutableStateOf<String?>(null) }
var showLanguageDialog by remember { mutableStateOf(false) }
var showCustomDialog by remember { mutableStateOf(false) }

View File

@@ -37,6 +37,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
@@ -44,6 +45,7 @@ import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.options.IFram
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.utils.loadOrCueVideo
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView
import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar
@@ -53,9 +55,10 @@ import eu.gaudian.translator.viewmodel.YouTubeExerciseState
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun YouTubeExerciseScreen(
navController: NavController,
exerciseViewModel: ExerciseViewModel
navController: NavController
) {
val activity = LocalContext.current.findActivity()
val exerciseViewModel : ExerciseViewModel = hiltViewModel(activity)
val context = LocalContext.current
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
val sessionParams by exerciseViewModel.youTubeSessionParams.collectAsState()

View File

@@ -40,7 +40,6 @@ import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import eu.gaudian.translator.BuildConfig
import eu.gaudian.translator.R
@@ -370,8 +369,9 @@ private fun DeveloperOptions(
navController: NavController,
) {
val context = LocalContext.current
val statusViewModel: StatusViewModel = viewModel()
val activity = context.findActivity()
val statusViewModel: StatusViewModel = hiltViewModel(viewModelStoreOwner = activity)
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)

View File

@@ -59,10 +59,11 @@ import kotlin.math.roundToInt
@Composable
fun VocabularyProgressOptionsScreen(
navController: NavController,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
) {
val activity = LocalContext.current.findActivity()
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val exportFileLauncher = rememberLauncherForActivityResult(

View File

@@ -2,7 +2,6 @@
package eu.gaudian.translator.view.settings
import android.app.Application
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
@@ -53,15 +52,17 @@ import eu.gaudian.translator.view.composable.SingleLanguageDropDown
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.StatusViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import okhttp3.internal.platform.PlatformRegistry.applicationContext
@Composable
fun VocabularyRepositoryOptionsScreen(
navController: NavController,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
statusViewModel: StatusViewModel = StatusViewModel.getInstance(applicationContext as Application)
navController: NavController
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val statusViewModel: StatusViewModel = hiltViewModel(viewModelStoreOwner = activity)
val context = LocalContext.current
val repositoryStateImportedFrom = stringResource(R.string.repository_state_imported_from)
@@ -79,7 +80,6 @@ fun VocabularyRepositoryOptionsScreen(
)
// CSV/Excel import state
val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val showTableImportDialog = remember { mutableStateOf(false) }
var parsedTable by remember { mutableStateOf<List<List<String>>>(emptyList()) }

View File

@@ -2,7 +2,6 @@
package eu.gaudian.translator.view.translation
import android.app.Application
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
@@ -64,16 +63,13 @@ import eu.gaudian.translator.view.hints.TranslationScreenHint
import eu.gaudian.translator.view.settings.SettingsRoutes
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.SettingsViewModel
import eu.gaudian.translator.viewmodel.StatusViewModel
import eu.gaudian.translator.viewmodel.TranslationViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import kotlinx.coroutines.launch
@Composable
fun TranslationScreen(
translationViewModel: TranslationViewModel,
languageViewModel: LanguageViewModel,
statusViewModel: StatusViewModel,
onHistoryClick: () -> Unit,
onSettingsClick: () -> Unit,
navController: NavHostController
@@ -82,10 +78,6 @@ fun TranslationScreen(
val activity = LocalContext.current.findActivity()
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel = remember(context) {
VocabularyViewModel.getInstance(context.applicationContext as Application)
}
val isInitializationComplete by settingsViewModel.isInitialized.collectAsStateWithLifecycle(
initialValue = true,
@@ -111,9 +103,7 @@ fun TranslationScreen(
LoadedTranslationContent(
translationViewModel = translationViewModel,
languageViewModel = languageViewModel,
statusViewModel = statusViewModel,
settingsViewModel = settingsViewModel,
vocabularyViewModel = vocabularyViewModel,
onHistoryClick = onHistoryClick,
onSettingsClick = onSettingsClick,
context = context
@@ -126,9 +116,7 @@ fun TranslationScreen(
private fun LoadedTranslationContent(
translationViewModel: TranslationViewModel,
languageViewModel: LanguageViewModel,
statusViewModel: StatusViewModel,
settingsViewModel: SettingsViewModel,
vocabularyViewModel: VocabularyViewModel,
onHistoryClick: () -> Unit,
onSettingsClick: () -> Unit,
context: Context
@@ -163,9 +151,6 @@ private fun LoadedTranslationContent(
if (showAddVocabularyDialog) {
AddVocabularyDialog(
statusViewModel = statusViewModel,
languageViewModel = languageViewModel,
vocabularyViewModel = vocabularyViewModel,
initialWord = inputText,
translation = translatedText,
onDismissRequest = { showAddVocabularyDialog = false },

View File

@@ -11,14 +11,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import eu.gaudian.translator.utils.TextToSpeechHelper
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.SettingsViewModel
import eu.gaudian.translator.viewmodel.StatusViewModel
import eu.gaudian.translator.viewmodel.TranslationViewModel
import kotlinx.coroutines.launch
@@ -26,11 +24,10 @@ import kotlinx.coroutines.launch
@Composable
fun TranslationScreen(
navController: NavController,
statusViewModel: StatusViewModel = viewModel(),
translationViewModel: TranslationViewModel = viewModel(),
) {
val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val translationViewModel: TranslationViewModel = hiltViewModel(viewModelStoreOwner = activity)
var showHistorySheet by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState()
@@ -42,7 +39,6 @@ fun TranslationScreen(
TranslationScreen(
translationViewModel = translationViewModel,
languageViewModel = languageViewModel,
statusViewModel = statusViewModel,
onHistoryClick = { showHistorySheet = true },
onSettingsClick = {
@Suppress("HardCodedStringLiteral")
@@ -92,7 +88,5 @@ fun TranslationScreenPreview() {
val navController = NavController(LocalContext.current)
TranslationScreen(
navController = navController,
statusViewModel = viewModel(),
translationViewModel = viewModel(),
)
}

View File

@@ -33,7 +33,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.TagCategory
@@ -63,10 +62,10 @@ fun CategoryDetailScreen(
modifier: Modifier = Modifier
) {
val activity = LocalContext.current.findActivity()
val categoryViewModel: CategoryViewModel = viewModel(viewModelStoreOwner = activity)
val progressViewModel: ProgressViewModel = ProgressViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(activity.application as android.app.Application)
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val progressViewModel: ProgressViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val category by categoryViewModel.getCategoryById(categoryId).collectAsState(initial = null)
val categoryProgressList by progressViewModel.categoryProgressList.collectAsState()
@@ -254,7 +253,6 @@ fun CategoryDetailScreen(
if (showDeleteItemsDialog) {
DeleteItemsDialog(
onDismiss = { categoryViewModel.setShowDeleteItemsDialog(false, categoryId) },
viewModel = vocabularyViewModel,
categoryId = categoryId
)
}

View File

@@ -31,11 +31,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
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.TagCategory
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar
@@ -57,12 +60,16 @@ enum class SortOption {
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CategoryListScreen(
categoryViewModel: CategoryViewModel,
progressViewModel: ProgressViewModel,
onNavigateBack: () -> Unit,
onCategoryClicked: (Int) -> Unit,
) {
val activity = LocalContext.current.findActivity()
val categoryViewModel : CategoryViewModel = hiltViewModel(activity)
val progressViewModel : ProgressViewModel = hiltViewModel(activity)
var sortOption by remember { mutableStateOf(SortOption.NAME) }
var sortMenuExpanded by remember { mutableStateOf(false) }
val categoryProgressList by progressViewModel.categoryProgressList.collectAsState(initial = emptyList())
@@ -75,7 +82,6 @@ fun CategoryListScreen(
if (showAddCategoryDialog) {
AddCategoryDialog(
onDismiss = { showAddCategoryDialog = false },
categoryViewModel = categoryViewModel,
)
}

View File

@@ -3,7 +3,6 @@
package eu.gaudian.translator.view.vocabulary
import android.annotation.SuppressLint
import android.app.Application
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing
@@ -89,14 +88,15 @@ fun DashboardContent(
onNavigateToCategoryDetail: (Int) -> Unit,
onNavigateToCategoryList: () -> Unit,
onShowWordPairExerciseDialog: () -> Unit,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as Application),
progressViewModel: ProgressViewModel = ProgressViewModel.getInstance(LocalContext.current.applicationContext as Application),
) {
val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
val progressViewModel: ProgressViewModel = hiltViewModel(viewModelStoreOwner = activity)
var showMissingLanguageDialog by remember { mutableStateOf(false) }
var selectedMissingLanguageId by remember { mutableStateOf<Int?>(null) }
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val affectedItems by remember(selectedMissingLanguageId) {
selectedMissingLanguageId?.let {
@@ -630,7 +630,6 @@ private fun LazyStatusWidget(
}
} else {
StatusWidget(
vocabularyViewModel = vocabularyViewModel,
onNavigateToNew = onNavigateToNew,
onNavigateToDuplicates = onNavigateToDuplicates,
onNavigateToFaulty = onNavigateToFaulty,

View File

@@ -34,6 +34,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -45,6 +46,7 @@ import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@@ -52,17 +54,27 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.LanguageLevels
import eu.gaudian.translator.model.MyAppLanguageLevel
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.viewmodel.ProgressViewModel
@Composable
fun LanguageProgressScreen(wordsLearned: Int, navController: NavController) {
fun LanguageProgressScreen(navController: NavController) {
val activity = LocalContext.current.findActivity()
val progressViewModel : ProgressViewModel = hiltViewModel(activity)
val wordsLearned = progressViewModel.totalWordsCompleted.collectAsState().value
AppScaffold(
topBar = {
AppTopAppBar(
@@ -368,5 +380,5 @@ private fun LevelDetailDialog(level: MyAppLanguageLevel, onDismiss: () -> Unit)
@Preview(showBackground = true)
@Composable
fun LanguageProgressScreenPreview() {
LanguageProgressScreen(wordsLearned = 50000, navController = NavController(androidx.compose.ui.platform.LocalContext.current))
LanguageProgressScreen(navController = NavController(LocalContext.current))
}

View File

@@ -2,7 +2,6 @@
package eu.gaudian.translator.view.vocabulary
import android.app.Application
import androidx.activity.ComponentActivity
import androidx.activity.compose.LocalActivity
import androidx.compose.animation.core.animateDpAsState
@@ -34,7 +33,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
@@ -59,7 +58,6 @@ import eu.gaudian.translator.view.composable.OptionItemSwitch
import eu.gaudian.translator.view.composable.TabItem
import eu.gaudian.translator.view.dialogs.StartExerciseDialog
import eu.gaudian.translator.view.dialogs.VocabularyMenu
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.ExerciseViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import kotlinx.coroutines.flow.first
@@ -94,9 +92,8 @@ fun MainVocabularyScreen(
) {
val activity = LocalActivity.current as ComponentActivity
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(activity.application as Application)
val categoryViewModel: CategoryViewModel = viewModel(viewModelStoreOwner = activity)
val exerciseViewModel: ExerciseViewModel = viewModel(viewModelStoreOwner = activity)
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val exerciseViewModel: ExerciseViewModel = hiltViewModel(activity)
val vocabularyNavController = rememberNavController()
val coroutineScope = rememberCoroutineScope()
var showCustomExerciseDialog by remember { mutableStateOf(false) }
@@ -117,8 +114,6 @@ fun MainVocabularyScreen(
if (showCustomExerciseDialog) {
StartExerciseDialog(
vocabularyViewModel = vocabularyViewModel,
categoryViewModel = categoryViewModel,
onDismiss = { showCustomExerciseDialog = false },
onConfirm = { categories, stages, languageIds ->
showCustomExerciseDialog = false
@@ -133,8 +128,6 @@ fun MainVocabularyScreen(
if (showWordPairExerciseDialog) {
StartExerciseDialog(
vocabularyViewModel = vocabularyViewModel,
categoryViewModel = categoryViewModel,
onDismiss = { showWordPairExerciseDialog = false },
onConfirm = { categories, stages, languageIds ->
// Store selections and open settings dialog instead of starting immediately

View File

@@ -30,11 +30,14 @@ 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.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
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.AppSlider
@@ -45,14 +48,16 @@ import kotlinx.coroutines.launch
@Composable
fun NoGrammarItemsScreen(
navController: NavController,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application)
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val itemsWithoutGrammar by vocabularyViewModel.allItemsWithoutGrammar.collectAsState()
val isGenerating by vocabularyViewModel.isGenerating.collectAsState()
var showFetchGrammarDialog by remember { mutableStateOf(false) }
@Suppress("UnusedVariable") val onClose = { navController.popBackStack() }
@Suppress("UnusedVariable", "unused", "HardCodedStringLiteral") val onClose = { navController.popBackStack() }
if (itemsWithoutGrammar.isEmpty() && !isGenerating) {
Column(

View File

@@ -2,7 +2,6 @@
package eu.gaudian.translator.view.vocabulary
import android.app.Application
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -32,20 +31,16 @@ import eu.gaudian.translator.view.LocalConnectionConfigured
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.dialogs.AddVocabularyDialog
import eu.gaudian.translator.view.dialogs.ImportVocabularyDialog
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.StatusViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
@Composable
fun NoVocabularyScreen(){
val application = LocalContext.current.applicationContext as Application
val statusViewModel = StatusViewModel.getInstance(application)
val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val vocabularyViewModel = VocabularyViewModel.getInstance(application)
val categoryViewModel = CategoryViewModel.getInstance(application)
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
var showAddVocabularyDialog by remember { mutableStateOf(false) }
@@ -56,9 +51,6 @@ fun NoVocabularyScreen(){
if (showAddVocabularyDialog) {
AddVocabularyDialog(
statusViewModel = statusViewModel,
languageViewModel = languageViewModel,
vocabularyViewModel = vocabularyViewModel,
onDismissRequest = { showAddVocabularyDialog = false }
)
}
@@ -66,7 +58,6 @@ fun NoVocabularyScreen(){
if (showImportVocabularyDialog) {
ImportVocabularyDialog(
languageViewModel = languageViewModel,
categoryViewModel = categoryViewModel,
vocabularyViewModel = vocabularyViewModel,
onDismiss = { showImportVocabularyDialog = false }
)

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.vocabulary
import android.app.Application
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
@@ -10,24 +9,28 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.vocabulary.widgets.DetailedStageProgressBar
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import okhttp3.internal.platform.PlatformRegistry.applicationContext
@Composable
fun StageDetailScreen(
navController: NavController,
stage: VocabularyStage,
viewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
) {
val activity = LocalContext.current.findActivity()
val viewModel = hiltViewModel<VocabularyViewModel>(viewModelStoreOwner = activity)
val stageMapping by viewModel.stageMapping.collectAsState()
val dueTodayItems by viewModel.dueTodayItems.collectAsState()

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.vocabulary
import android.app.Application
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
@@ -79,9 +78,9 @@ fun StartScreen(
selectedTargetLanguage: Language?,
onTargetLanguageChanged: (Language?) -> Unit,
) {
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(
LocalContext.current.applicationContext as Application
)
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val dueTodayItems by vocabularyViewModel.dueTodayItems.collectAsState(initial = emptyList())
val allItems = cardSet?.cards ?: emptyList()

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.vocabulary
import android.app.Application
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -28,7 +27,6 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.VocabularyItem
@@ -44,7 +42,6 @@ import eu.gaudian.translator.view.dialogs.CategorySelectionDialog
import eu.gaudian.translator.view.dialogs.ImportVocabularyDialog
import eu.gaudian.translator.view.dialogs.StageSelectionDialog
import eu.gaudian.translator.view.vocabulary.card.VocabularyCard
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import kotlinx.coroutines.launch
@@ -61,9 +58,8 @@ fun VocabularyCardHost(
onBackPressed: (() -> Unit)? = null
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as Application)
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val categoryViewModel: CategoryViewModel = viewModel()
val scope = rememberCoroutineScope()
val navigationItems by vocabularyViewModel.currentNavigationItems.collectAsState()
@@ -161,6 +157,7 @@ fun VocabularyCardHost(
lifecycle = lifecycleOwner.lifecycle
)
@Suppress("ControlFlowWithEmptyBody")
if (exerciseMode && onBackPressed != null) {
//TODO consider BackPressHandler(onBackPressed = { showQuitDialog = true })
}
@@ -213,7 +210,6 @@ fun VocabularyCardHost(
onDeleteClick = { showDeleteDialog = true },
navController = navController,
isUserSpellingCorrect = false,
vocabularyViewModel = vocabularyViewModel
)
// Dialogs are unaffected by the layout change
@@ -237,7 +233,6 @@ fun VocabularyCardHost(
if (showCategoryDialog) {
CategorySelectionDialog(
viewModel = categoryViewModel,
onCategorySelected = {
vocabularyViewModel.addVocabularyItemToCategories(
listOf(currentVocabularyItem),
@@ -270,12 +265,12 @@ fun VocabularyCardHost(
languageViewModel = languageViewModel,
optionalDescription = stringResource(R.string.generate_related_vocabulary_items),
optionalSearchTerm = currentVocabularyItem.wordFirst,
categoryViewModel = categoryViewModel,
vocabularyViewModel = vocabularyViewModel
)
}
LaunchedEffect(spellingMode) {
@Suppress("ControlFlowWithEmptyBody")
if (spellingMode) {
//TODO implement // Setup spelling mode logic if needed
}

View File

@@ -50,7 +50,6 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.ComponentDefaults
import eu.gaudian.translator.view.vocabulary.card.VocabularyCard
import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
/**
* Represents the different types of exercises available.
@@ -140,15 +139,14 @@ sealed interface VocabularyExerciseAction {
fun GuessingExercise(
state: VocabularyExerciseState.Guessing,
navController: NavController,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
) {
VocabularyCard(
vocabularyItem = state.item,
isFlipped = state.isRevealed,
navController = navController,
exerciseMode = true,
switchOrder = state.isSwitched,
vocabularyViewModel = vocabularyViewModel
)
}
@@ -158,8 +156,8 @@ fun GuessingExercise(
fun SpellingExercise(
state: VocabularyExerciseState.Spelling,
navController: NavController,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
) {
VocabularyCard(
vocabularyItem = state.item,
isFlipped = state.isRevealed,
@@ -168,7 +166,6 @@ fun SpellingExercise(
navController = navController,
exerciseMode = true,
switchOrder = state.isSwitched,
vocabularyViewModel = vocabularyViewModel
)
}

View File

@@ -23,11 +23,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
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.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppAlertDialog
import eu.gaudian.translator.viewmodel.ExerciseConfig
import eu.gaudian.translator.viewmodel.ScreenState
@@ -49,8 +51,9 @@ fun VocabularyExerciseHostScreen(
onClose: () -> Unit,
navController: NavController
) {
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application)
val exerciseViewModel: VocabularyExerciseViewModel = viewModel()
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val exerciseViewModel: VocabularyExerciseViewModel = hiltViewModel(viewModelStoreOwner = activity)
val cardSet by vocabularyViewModel.cardSet.collectAsState()
val screenState by exerciseViewModel.screenState.collectAsState()

View File

@@ -42,14 +42,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar
@@ -75,8 +78,11 @@ private data class HeatmapMonth(val yearMonth: YearMonth, val weeks: List<List<L
@OptIn(ExperimentalTime::class)
@Composable
fun VocabularyHeatmapScreen(
viewModel: ProgressViewModel = ProgressViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application), navController: NavController
navController: NavController
) {
val activity = LocalContext.current.findActivity()
val viewModel : ProgressViewModel = hiltViewModel(activity)
// State and derived data
val dailyStats by viewModel.dailyVocabularyStats.collectAsState()
val today = remember { Clock.System.todayIn(TimeZone.currentSystemDefault()) }

View File

@@ -71,7 +71,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.R
@@ -126,9 +125,9 @@ fun VocabularyListScreen(
val scope = rememberCoroutineScope()
val activity = LocalContext.current.findActivity()
val lazyListState = rememberLazyListState()
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val categoryViewModel: CategoryViewModel = viewModel()
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val context = LocalContext.current
var filterState by rememberSaveable {
@@ -380,7 +379,6 @@ fun VocabularyListScreen(
showFilterSheet = false
scope.launch { lazyListState.scrollToItem(0) }
},
categoryViewModel = categoryViewModel,
languageViewModel = languageViewModel,
languagesPresent = allLanguages.filter { it.nameResId in languagesPresent },
hideCategory = categoryId != null && categoryId != 0,
@@ -391,7 +389,6 @@ fun VocabularyListScreen(
if (showCategoryDialog) {
val selectedItems = vocabularyItems.filter { selection.contains(it.id.toLong()) }
CategorySelectionDialog(
viewModel = categoryViewModel,
onCategorySelected = {
vocabularyViewModel.addVocabularyItemToCategories(
selectedItems,
@@ -805,7 +802,6 @@ fun LanguageRowPreview() {
@Composable
private fun FilterSortBottomSheet(
currentFilterState: VocabularyFilterState,
categoryViewModel: CategoryViewModel,
languageViewModel: LanguageViewModel,
languagesPresent: List<Language>,
onDismiss: () -> Unit,
@@ -821,8 +817,9 @@ private fun FilterSortBottomSheet(
val context = LocalContext.current
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val activity = LocalContext.current.findActivity()
val languageConfigViewModel: LanguageConfigViewModel = viewModel()
val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(activity)
val allWordClasses by languageConfigViewModel.allWordClasses.collectAsStateWithLifecycle()
ModalBottomSheet(
@@ -896,7 +893,6 @@ private fun FilterSortBottomSheet(
Text(stringResource(R.string.label_category), style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(8.dp))
CategoryDropdown(
categoryViewModel = categoryViewModel,
initialCategoryId = selectedCategoryId,
onCategorySelected = { categories ->
selectedCategoryId = categories.firstOrNull()?.id
@@ -958,11 +954,9 @@ private fun FilterSortBottomSheet(
@Composable
fun FilterSortBottomSheetPreview() {
val activity = LocalContext.current.findActivity()
val categoryViewModel: CategoryViewModel = viewModel()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
FilterSortBottomSheet(
currentFilterState = VocabularyFilterState(),
categoryViewModel = categoryViewModel,
languageViewModel = languageViewModel,
languagesPresent = emptyList(),
onDismiss = {},

View File

@@ -56,7 +56,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
@@ -82,7 +81,6 @@ import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.internal.platform.PlatformRegistry.applicationContext
enum class FilterMode {
NEW, DUPLICATES, FAULTY
@@ -90,11 +88,14 @@ enum class FilterMode {
@Composable
fun VocabularySortingScreen(
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as android.app.Application),
categoryViewModel: CategoryViewModel = CategoryViewModel.getInstance(applicationContext as android.app.Application),
navController: NavHostController,
initialFilterMode: String? = null
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
var sortOrder by remember { mutableStateOf(VocabularyViewModel.SortOrder.NEWEST_FIRST) }
val allDuplicateItems by vocabularyViewModel.allDuplicateItems.collectAsState()
val allFaultyItems by vocabularyViewModel.allFaultyItems.collectAsState()
@@ -263,9 +264,7 @@ fun VocabularySortingScreen(
VocabularySortingItem(
item = item,
filterMode = filterMode, // NEW: Pass the mode to the item
vocabularyViewModel = vocabularyViewModel,
categoryViewModel = categoryViewModel,
languageConfigViewModel = viewModel()
vocabularyViewModel = vocabularyViewModel
)
}
}
@@ -286,8 +285,6 @@ fun VocabularySortingScreen(
@Composable
fun VocabularySortingScreenPreview() {
VocabularySortingScreen(
vocabularyViewModel = viewModel(),
categoryViewModel = viewModel(),
navController = NavHostController(LocalContext.current),
initialFilterMode = "NEW"
)
@@ -296,12 +293,11 @@ fun VocabularySortingScreenPreview() {
@Composable
fun VocabularySortingItem(
item: VocabularyItem,
filterMode: FilterMode?, // NEW: Receive the current filter mode
vocabularyViewModel: VocabularyViewModel,
categoryViewModel: CategoryViewModel,
languageConfigViewModel: LanguageConfigViewModel = viewModel()
filterMode: FilterMode?,
vocabularyViewModel: VocabularyViewModel
) {
val activity = LocalContext.current.findActivity()
val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
var wordFirst by remember { mutableStateOf(item.wordFirst) }
var wordSecond by remember { mutableStateOf(item.wordSecond) }
@@ -487,7 +483,6 @@ fun VocabularySortingItem(
}
CategoryDropdown(
categoryViewModel = categoryViewModel,
onCategorySelected = { categories ->
selectedCategories = categories.mapNotNull { it?.id }
},
@@ -560,9 +555,7 @@ fun VocabularySortingItemPreview() {
VocabularySortingItem(
item = item,
filterMode = FilterMode.NEW,
vocabularyViewModel = viewModel(),
categoryViewModel = viewModel(),
languageConfigViewModel = viewModel()
vocabularyViewModel = hiltViewModel()
)
}

View File

@@ -69,7 +69,7 @@ fun AdditionalContentBottomSheet(
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application)
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val coroutineScope = rememberCoroutineScope()
@@ -225,7 +225,6 @@ fun AdditionalContentBottomSheet(
SynonymsDisplay(
synonyms = synonyms,
loading = synonymsLoading,
vocabularyViewModel = vocabularyViewModel,
onSynonymAdded = handleSynonymAdded,
onReload = refreshSynonyms
)
@@ -320,10 +319,12 @@ private fun ExampleSentencesDisplayLoadedPreview() {
private fun SynonymsDisplay(
synonyms: List<SynonymDisplayState>?,
loading: Boolean,
vocabularyViewModel: VocabularyViewModel,
onSynonymAdded: () -> Unit,
onReload: () -> Unit,
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
Column {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -387,7 +388,7 @@ private fun SynonymsDisplay(
@Composable
private fun SynonymsDisplayLoadingPreview() {
// This preview doesn't have a real VocabularyViewModel, so actions will not work.
SynonymsDisplay(synonyms = null, loading = false, vocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application), onSynonymAdded = {}, onReload = {})
SynonymsDisplay(synonyms = null, loading = false, onSynonymAdded = {}, onReload = {})
}
@Suppress("HardCodedStringLiteral")
@@ -415,7 +416,7 @@ private fun SynonymsDisplayLoadedPreview() {
proximity = null
)
)
SynonymsDisplay(synonyms = synonyms, loading = false, vocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application), onSynonymAdded = {}, onReload = {})
SynonymsDisplay(synonyms = synonyms, loading = false, onSynonymAdded = {}, onReload = {})
}
@Composable

View File

@@ -29,11 +29,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.grammar.CategoryConfig
@@ -42,6 +43,7 @@ import eu.gaudian.translator.model.grammar.GrammaticalFeature
import eu.gaudian.translator.model.grammar.LanguageConfig
import eu.gaudian.translator.model.grammar.formatGrammarDetails
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.DialogButton
@@ -55,7 +57,8 @@ internal fun GrammarDetailsText(
) {
Log.d("GrammarComponents", "GrammarDetailsText called with details: $details, language: $language, isRevealed: $isRevealed")
val configViewModel: LanguageConfigViewModel = viewModel()
val activity = LocalContext.current.findActivity()
val configViewModel: LanguageConfigViewModel = hiltViewModel(activity)
val allConfigs by configViewModel.configs.collectAsState()
val languageConfig = language?.code?.let { allConfigs[it] }
val categoryConfig = details?.category?.let { languageConfig?.categories?.get(it) }
@@ -89,7 +92,8 @@ internal fun GrammarEditDialog(
) {
Log.d("GrammarComponents", "GrammarEditDialog called with feature: $feature, language: $language")
val configViewModel: LanguageConfigViewModel = viewModel()
val activity = LocalContext.current.findActivity()
val configViewModel: LanguageConfigViewModel = hiltViewModel(activity)
val allConfigs by configViewModel.configs.collectAsState()
val languageConfig = allConfigs[language.code]

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.vocabulary.card
import android.app.Application
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
@@ -54,7 +53,6 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
@@ -79,7 +77,6 @@ import eu.gaudian.translator.viewmodel.MessageDisplayType
import eu.gaudian.translator.viewmodel.SettingsViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import kotlinx.coroutines.launch
import okhttp3.internal.platform.PlatformRegistry.applicationContext
@Composable
@@ -95,12 +92,10 @@ fun VocabularyCard(
onDeleteClick: () -> Unit = {},
userSpellingAnswer: String? = null,
isUserSpellingCorrect: Boolean? = null,
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
@@ -463,7 +458,8 @@ private fun GrammarPill(
isRevealed: Boolean,
onEditGrammarClick: () -> Unit
) {
val configViewModel: LanguageConfigViewModel = viewModel()
val activity = LocalContext.current.findActivity()
val configViewModel: LanguageConfigViewModel = hiltViewModel(viewModelStoreOwner = activity)
val allConfigs by configViewModel.configs.collectAsState()
val languageConfig = language?.code?.let { allConfigs[it] }
val categoryConfig = wordDetails?.category?.let { languageConfig?.categories?.get(it) }

View File

@@ -35,14 +35,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.TagCategory
import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.ui.theme.ThemePreviews
import eu.gaudian.translator.ui.theme.semanticColors
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.SecondaryButton
import eu.gaudian.translator.viewmodel.ProgressViewModel
@@ -52,7 +55,9 @@ fun CategoryProgressWidget(
onCategoryClicked: (VocabularyCategory?) -> Unit,
onViewAllClicked: () -> Unit
) {
val viewModel: ProgressViewModel = ProgressViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application)
val activity = LocalContext.current.findActivity()
val viewModel : ProgressViewModel = hiltViewModel(viewModelStoreOwner = activity)
val categoryProgressList by viewModel.categoryProgressList.collectAsState(initial = emptyList())
val selectedCategories by viewModel.selectedCategories.collectAsState(initial = emptySet())

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.vocabulary.widgets
import android.app.Application
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -17,24 +16,28 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.ui.theme.semanticColors
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.viewmodel.VocabularyViewModel
import okhttp3.internal.platform.PlatformRegistry.applicationContext
@Composable
fun StatusWidget(
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
onNavigateToNew: () -> Unit,
onNavigateToDuplicates: () -> Unit,
onNavigateToFaulty: () -> Unit,
onNavigateToNoGrammar: () -> Unit,
onNavigateToMissingLanguage: (Int) -> Unit
) {
val activity = LocalContext.current.findActivity()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val newItemsCount by vocabularyViewModel.newItemsCount.collectAsState()
val duplicateCount by vocabularyViewModel.duplicateItemsCount.collectAsState()
val faultyItemsCount by vocabularyViewModel.faultyItemsCount.collectAsState()

View File

@@ -5,8 +5,8 @@ package eu.gaudian.translator.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.VocabularyCategory
//import eu.gaudian.translator.model.VocabularyDictionary
import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.Log
import kotlinx.coroutines.flow.Flow
@@ -18,35 +18,13 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
/**
* TODO: Convert CategoryViewModel to HiltViewModel
*
* This ViewModel needs to be converted to use Hilt for dependency injection.
* Currently it manually instantiates dependencies and uses a singleton pattern.
*
* Checklist:
* - [ ] Add @HiltViewModel annotation to the class
* - [ ] Change constructor to use @Inject and inject dependencies:
* - VocabularyRepository
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
* - VocabularyRepository
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
* - [ ] Create CategoryViewModelEntryPoint interface for accessing the singleton instance
* - [ ] Remove manual dependency instantiation from constructor and init block
* - [ ] Update all places where CategoryViewModel.getInstance() is called to ensure compatibility
* - [ ] Test that the ViewModel works correctly with dependency injection
*/
class CategoryViewModel(application: Application) : AndroidViewModel(application) {
companion object {
@Volatile private var INSTANCE: CategoryViewModel? = null
fun getInstance(application: Application): CategoryViewModel = INSTANCE ?: synchronized(this) {
INSTANCE ?: CategoryViewModel(application).also { INSTANCE = it }
}
}
private val repository: VocabularyRepository = VocabularyRepository.getInstance(application)
@HiltViewModel
class CategoryViewModel @Inject constructor(
application: Application,
private val repository: VocabularyRepository
) : AndroidViewModel(application) {
val categories: StateFlow<List<VocabularyCategory>> = repository.getAllCategoriesFlow()
.stateIn(
@@ -146,5 +124,4 @@ class CategoryViewModel(application: Application) : AndroidViewModel(application
_showEditCategoryDialog.value = show
categoryToEdit = categoryId
}
}

View File

@@ -13,35 +13,22 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.utils.CorrectionService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
/**
* TODO: Convert CorrectionViewModel to HiltViewModel
*
* This ViewModel needs to be converted to use Hilt for dependency injection.
* Currently it manually instantiates dependencies in the constructor.
*
* Checklist:
* - [ ] Add @HiltViewModel annotation to the class
* - [ ] Change constructor to use @Inject and inject dependencies:
* - CorrectionService
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
* - CorrectionService
* - [ ] Remove manual dependency instantiation from constructor
* - [ ] Update all places where CorrectionViewModel() is instantiated to use Hilt injection
* - [ ] Test that the ViewModel works correctly with dependency injection
*/
class CorrectionViewModel(application: Application) : AndroidViewModel(application) {
@HiltViewModel
class CorrectionViewModel @Inject constructor(
application: Application,
private val correctionService: CorrectionService
) : AndroidViewModel(application) {
enum class Tone { NONE, FORMAL, CASUAL, COLLOQUIAL, POLITE, PROFESSIONAL, FRIENDLY, ACADEMIC, CREATIVE }
val correctionService = CorrectionService(application.applicationContext)
private val _textFieldValue = MutableStateFlow(TextFieldValue(""))
val textFieldValue = _textFieldValue.asStateFlow()

View File

@@ -6,6 +6,7 @@ import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.R
import eu.gaudian.translator.model.CategorizationQuestion
import eu.gaudian.translator.model.Exercise
@@ -35,6 +36,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.reflect.KClass
sealed class AnswerResult {
@@ -85,45 +87,21 @@ sealed class YouTubeExerciseState {
data class Error(val message: String) : YouTubeExerciseState()
}
/**
* TODO: Convert ExerciseViewModel to HiltViewModel
*
* This ViewModel needs to be converted to use Hilt for dependency injection.
* Currently it manually instantiates dependencies and uses a singleton pattern.
*
* Checklist:
* - [ ] Add @HiltViewModel annotation to the class
* - [ ] Change constructor to use @Inject and inject dependencies:
* - ExerciseRepository
* - ExerciseService
* - VocabularyRepository
* - LanguageRepository
* - TranslationService
* - YouTubeApiService
* - YouTubeExerciseService
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
* - ExerciseRepository
* - ExerciseService
* - VocabularyRepository
* - LanguageRepository
* - TranslationService
* - YouTubeApiService
* - YouTubeExerciseService
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
* - [ ] Create ExerciseViewModelEntryPoint interface for accessing the singleton instance
* - [ ] Remove manual dependency instantiation from constructor and init block
* - [ ] Update all places where ExerciseViewModel.getInstance() is called to ensure compatibility
* - [ ] Test that the ViewModel works correctly with dependency injection
*/
class ExerciseViewModel(application: Application) : AndroidViewModel(application) {
@HiltViewModel
class ExerciseViewModel @Inject constructor(
application: Application,
private val exerciseRepository: ExerciseRepository,
private val exerciseService: ExerciseService,
private val vocabularyRepository: VocabularyRepository,
private val languageRepository: LanguageRepository,
private val translationService: TranslationService,
private val youTubeApiService: YouTubeApiService,
private val youTubeExerciseService: YouTubeExerciseService
) : AndroidViewModel(application) {
data class YouTubeSessionParams(val url: String, val sourceLanguage: String?, val targetLanguage: String?)
companion object {
@SuppressLint("StaticFieldLeak")
@Volatile private var INSTANCE: ExerciseViewModel? = null
fun getInstance(application: Application): ExerciseViewModel = INSTANCE ?: synchronized(this) {
INSTANCE ?: ExerciseViewModel(application).also { INSTANCE = it }
}
}
val context = application.applicationContext!!
private fun normalizeText(input: String?): String {
if (input.isNullOrBlank()) return ""
@@ -141,18 +119,6 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
if (nk.isNotBlank() && nv.isNotBlank()) nk to nv else null
}.toMap()
@SuppressLint("StaticFieldLeak")
val context = application.applicationContext!!
val exerciseRepository = ExerciseRepository(application.applicationContext)
val exerciseService = ExerciseService(application.applicationContext)
val vocabularyRepository = VocabularyRepository.getInstance(application.applicationContext)
// Translation helpers
private val languageRepository = LanguageRepository(application.applicationContext)
private val translationService = TranslationService(application.applicationContext)
private var currentSubtitleTranslationJob: Job? = null
private val _exercises = MutableStateFlow<List<Exercise>>(emptyList())
val exercises: StateFlow<List<Exercise>> = _exercises.asStateFlow()
@@ -169,7 +135,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
val youTubeExerciseState: StateFlow<YouTubeExerciseState> = _youTubeExerciseState.asStateFlow()
private var currentlyFetchingVideoId: String? = null
private var currentSubtitleTranslationJob: Job? = null
init {
loadExercisesFromRepository()
@@ -412,7 +378,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
_youTubeExerciseState.value = YouTubeExerciseState.Loading
Log.d("ExerciseViewModel", "State set to Loading for videoId=$videoId")
try {
val title = YouTubeApiService.getVideoTitle(videoId)
val title = youTubeApiService.getVideoTitle(videoId)
Log.i("ExerciseViewModel", "Fetched video title: '${title.take(100)}'")
var available: List<String> = emptyList()
@@ -424,7 +390,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
delay(2500)
}
try {
available = YouTubeExerciseService.listTranscripts(videoId)
available = youTubeExerciseService.listTranscripts(videoId)
} catch (e: Exception) {
Log.w("ExerciseViewModel", "listTranscripts failed on attempt $attempts", e)
}
@@ -441,7 +407,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
Log.d("ExerciseViewModel", "Chosen transcript language: $chosenLang (preferred was $langCode)")
val finalLang = chosenLang ?: throw Exception("No transcripts available for this video.")
val subtitles = YouTubeExerciseService.getTranscript(videoId, finalLang)
val subtitles = youTubeExerciseService.getTranscript(videoId, finalLang)
Log.i("ExerciseViewModel", "Fetched ${subtitles.size} subtitle lines for $videoId in lang=${finalLang}")
_youTubeExerciseState.value = YouTubeExerciseState.Success(

View File

@@ -5,41 +5,22 @@ package eu.gaudian.translator.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.grammar.LanguageConfig
import eu.gaudian.translator.utils.Log
import kotlinx.coroutines.flow.MutableStateFlow
import eu.gaudian.translator.model.repository.LanguageConfigRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import javax.inject.Inject
/**
* TODO: Convert LanguageConfigViewModel to HiltViewModel
*
* This ViewModel needs to be converted to use Hilt for dependency injection.
* Currently it manually instantiates dependencies in the constructor.
*
* Checklist:
* - [ ] Add @HiltViewModel annotation to the class
* - [ ] Change constructor to use @Inject and inject dependencies:
* - Application (provided by Hilt)
* - [ ] Remove manual dependency instantiation from constructor
* - [ ] Update all places where LanguageConfigViewModel() is instantiated to use Hilt injection
* - [ ] Test that the ViewModel works correctly with dependency injection
*/
class LanguageConfigViewModel(application: Application) : AndroidViewModel(application) {
@HiltViewModel
class LanguageConfigViewModel @Inject constructor(
application: Application,
private val languageConfigRepository: LanguageConfigRepository
) : AndroidViewModel(application) {
private val _configs = MutableStateFlow<Map<String, LanguageConfig>>(emptyMap())
val configs = _configs.asStateFlow()
private val jsonParser = Json { ignoreUnknownKeys = true }
init {
loadAllConfigs()
}
val configs: StateFlow<Map<String, LanguageConfig>> = languageConfigRepository.configs
val allWordClasses: StateFlow<List<String>> = configs.map { configsMap ->
configsMap.values
@@ -48,44 +29,15 @@ class LanguageConfigViewModel(application: Application) : AndroidViewModel(appli
.sorted()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
/**
* Loads all language configuration files from the "language_configs" asset directory.
*/
private fun loadAllConfigs() {
viewModelScope.launch {
val context = getApplication<Application>().applicationContext
try {
val configFiles = context.assets.list("language_configs") ?: return@launch
val loadedConfigs = mutableMapOf<String, LanguageConfig>()
for (fileName in configFiles) {
if (fileName.endsWith(".json")) {
val jsonString = context.assets.open("language_configs/$fileName")
.bufferedReader()
.use { it.readText() }
val config = jsonParser.decodeFromString<LanguageConfig>(jsonString)
loadedConfigs[config.language_code] = config
}
}
_configs.value = loadedConfigs
} catch (e: Exception) {
// Log the error for debugging, but don't crash the app
Log.e("Failed to load language configs: ${e.message}")
e.printStackTrace()
}
}
}
/**
* Retrieves the configuration for a specific language code.
*
* @param langCode The ISO language code (e.g., "de").
* @return The LanguageConfig for the given code, or null if not found.
*/
@Suppress("unused")
fun getConfigForLanguage(langCode: String): LanguageConfig? {
Log.d("Fetching config for language: $langCode")
return _configs.value[langCode]
return languageConfigRepository.getConfigForLanguage(langCode)
}
/**
@@ -97,12 +49,6 @@ class LanguageConfigViewModel(application: Application) : AndroidViewModel(appli
* the language code is not found or if the language has no articles defined.
*/
fun getArticlesForLanguage(langCode: String): Set<String> {
return try {
val config = _configs.value[langCode]
config?.articles?.toSet() ?: emptySet()
} catch (e: Exception) {
Log.e("Error retrieving articles for '$langCode': ${e.message}")
emptySet()
}
return languageConfigRepository.getArticlesForLanguage(langCode)
}
}

View File

@@ -29,14 +29,12 @@ import javax.inject.Inject
*/
@HiltViewModel
class LanguageViewModel @Inject constructor(
application: Application
application: Application,
private val languageRepository: LanguageRepository
) : AndroidViewModel(application) {
val languageRepository = LanguageRepository(application)
private val languageSwitchMutex = Mutex()
// Enabled languages (visible across the app)
val allLanguages: StateFlow<List<Language>> = languageRepository.loadLanguages(LanguageListType.ALL)
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
@@ -68,8 +66,6 @@ class LanguageViewModel @Inject constructor(
suspend fun getLanguageById(id: Int): Language {
val languages = allLanguages.first { it.isNotEmpty() }
val language = languages.find { it.nameResId == id }

View File

@@ -2,8 +2,8 @@ package eu.gaudian.translator.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.application
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.model.repository.SettingsRepository
@@ -26,6 +26,7 @@ import kotlinx.datetime.minus
import kotlinx.datetime.toLocalDateTime
import java.text.DateFormatSymbols
import java.util.Locale
import javax.inject.Inject
data class CategoryProgress(
@@ -53,38 +54,13 @@ data class StageStats(
val itemCount: Int
)
/**
* TODO: Convert ProgressViewModel to HiltViewModel
*
* This ViewModel needs to be converted to use Hilt for dependency injection.
* Currently it manually instantiates dependencies and uses a singleton pattern.
*
* Checklist:
* - [ ] Add @HiltViewModel annotation to the class
* - [ ] Change constructor to use @Inject and inject dependencies:
* - VocabularyRepository
* - SettingsRepository
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
* - VocabularyRepository
* - SettingsRepository
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
* - [ ] Create ProgressViewModelEntryPoint interface for accessing the singleton instance
* - [ ] Remove manual dependency instantiation from constructor and init block
* - [ ] Update all places where ProgressViewModel.getInstance() is called to ensure compatibility
* - [ ] Test that the ViewModel works correctly with dependency injection
*/
class ProgressViewModel(application: Application) : AndroidViewModel(application) {
companion object {
@Volatile private var INSTANCE: ProgressViewModel? = null
fun getInstance(application: Application): ProgressViewModel = INSTANCE ?: synchronized(this) {
INSTANCE ?: ProgressViewModel(application).also { INSTANCE = it }
}
}
val vocabularyRepository = VocabularyRepository.getInstance(application.applicationContext)
val settingsRepository = SettingsRepository(application.applicationContext)
@Suppress("SameParameterValue")
@HiltViewModel
class ProgressViewModel @Inject constructor(
application: Application,
private val vocabularyRepository: VocabularyRepository,
private val settingsRepository: SettingsRepository
) : AndroidViewModel(application) {
// Single-flight + coalescing scheduler for refresh()
private val refreshMutex = Mutex()
@@ -204,7 +180,6 @@ class ProgressViewModel(application: Application) : AndroidViewModel(application
}
suspend fun getWeeklyActivityStats(): List<WeeklyActivityStat> {
val vocabularyRepository = VocabularyRepository.getInstance(application)
val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
// 1. Create a list of the last 7 dates in chronological order.

View File

@@ -5,6 +5,7 @@ package eu.gaudian.translator.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.StatusAction
import eu.gaudian.translator.utils.StatusMessageService
@@ -17,6 +18,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.util.LinkedList
import java.util.Queue
import javax.inject.Inject
enum class MessageAction {
NAVIGATE_TO_API_KEYS
@@ -43,31 +45,11 @@ enum class MessageDisplayType(val priority: Int) {
ACTIONABLE_ERROR(5)
}
/**
* TODO: Convert StatusViewModel to HiltViewModel
*
* This ViewModel needs to be converted to use Hilt for dependency injection.
* Currently it manually instantiates dependencies and uses a singleton pattern.
*
* Checklist:
* - [ ] Add @HiltViewModel annotation to the class
* - [ ] Change constructor to use @Inject and inject dependencies:
* - StatusMessageService
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
* - StatusMessageService
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
* - [ ] Create StatusViewModelEntryPoint interface for accessing the singleton instance
* - [ ] Remove manual dependency instantiation from constructor and init block
* - [ ] Update all places where StatusViewModel.getInstance() is called to ensure compatibility
* - [ ] Test that the ViewModel works correctly with dependency injection
*/
class StatusViewModel(application: Application) : AndroidViewModel(application) {
companion object {
@Volatile private var INSTANCE: StatusViewModel? = null
fun getInstance(application: Application): StatusViewModel = INSTANCE ?: synchronized(this) {
INSTANCE ?: StatusViewModel(application).also { INSTANCE = it }
}
}
@HiltViewModel
class StatusViewModel @Inject constructor(
application: Application,
private val statusMessageService: StatusMessageService
) : AndroidViewModel(application) {
private val messageQueue: Queue<Pair<String, MessageDisplayType>> = LinkedList()
private var messageDisplayJob: Job? = null
@@ -79,7 +61,7 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
init {
viewModelScope.launch {
StatusMessageService.actions.collect { action ->
statusMessageService.actions.collect { action ->
handleAction(action)
}
}
@@ -100,13 +82,11 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
}
fun showApiKeyMissingMessage() = viewModelScope.launch {
StatusMessageService.trigger(
StatusAction.ShowActionableMessage(
statusMessageService.showActionableMessage(
text = "API Key is missing or invalid.",
type = MessageDisplayType.ACTIONABLE_ERROR,
action = MessageAction.NAVIGATE_TO_API_KEYS
)
)
}
private fun showPermanentActionableMessageInternal(message: String, type: MessageDisplayType, action: MessageAction) {
@@ -120,43 +100,43 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
}
fun showPermanentMessage(message: String, type: MessageDisplayType) = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.ShowPermanentMessage(message, type))
statusMessageService.showPermanentMessage(message, type)
}
fun cancelPermanentMessage() = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.CancelPermanentMessage)
statusMessageService.cancelPermanentMessage()
}
fun performLoadingOperation(block: suspend () -> Unit) = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.PerformLoadingOperation(block))
statusMessageService.trigger(StatusAction.PerformLoadingOperation(block))
}
fun cancelLoadingOperation() = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.CancelLoadingOperation)
statusMessageService.trigger(StatusAction.CancelLoadingOperation)
}
fun showInfoMessage(message: String, timeoutInSeconds: Int = 3) = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.ShowMessage(message, MessageDisplayType.INFO, timeoutInSeconds))
statusMessageService.showInfoMessage(message, timeoutInSeconds)
}
fun showLoadingMessage(message: String, timeoutInSeconds: Int = 0) = viewModelScope.launch { // Default timeout 0 for indefinite
StatusMessageService.trigger(StatusAction.ShowMessage(message, MessageDisplayType.LOADING, timeoutInSeconds))
statusMessageService.showLoadingMessage(message, timeoutInSeconds)
}
fun showErrorMessage(message: String, timeoutInSeconds: Int = 5) = viewModelScope.launch { // Default timeout 5 for errors
StatusMessageService.trigger(StatusAction.ShowMessage(message, MessageDisplayType.ERROR, timeoutInSeconds))
statusMessageService.showErrorMessage(message, timeoutInSeconds)
}
fun showSuccessMessage(message: String, timeoutInSeconds: Int = 3) = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.ShowMessage(message, MessageDisplayType.SUCCESS, timeoutInSeconds))
statusMessageService.showSuccessMessage(message, timeoutInSeconds)
}
fun hideMessageBar() = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.HideMessageBar)
statusMessageService.hideMessageBar()
}
fun cancelAllMessages() = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.CancelAllMessages)
statusMessageService.cancelAllMessages()
}
private fun cancelPermanentMessageInternal() {

View File

@@ -5,6 +5,7 @@ package eu.gaudian.translator.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.LanguageModel
import eu.gaudian.translator.model.TranslationHistoryItem
import eu.gaudian.translator.model.repository.ApiRepository
@@ -20,33 +21,17 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
/**
* TODO: Convert TranslationViewModel to HiltViewModel
*
* This ViewModel needs to be converted to use Hilt for dependency injection.
* Currently it manually instantiates dependencies in the constructor.
*
* Checklist:
* - [ ] Add @HiltViewModel annotation to the class
* - [ ] Change constructor to use @Inject and inject dependencies:
* - TranslationService
* - ApiRepository
* - TextToSpeechHelper (if needed)
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
* - TranslationService
* - ApiRepository
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
* - [ ] Create TranslationViewModelEntryPoint interface for accessing the singleton instance
* - [ ] Remove manual dependency instantiation from constructor and init block
* - [ ] Update all places where TranslationViewModel.getInstance() is called to ensure compatibility
* - [ ] Test that the ViewModel works correctly with dependency injection
*/
class TranslationViewModel(application: Application) : AndroidViewModel(application) {
@HiltViewModel
class TranslationViewModel @Inject constructor(
application: Application,
private val translationService: TranslationService,
private val apiRepository: ApiRepository,
val languageRepository: LanguageRepository
) : AndroidViewModel(application) {
// For back/forward navigation of history in the UI (like editors)
val languageRepository = LanguageRepository(application)
private val _historyCursor = MutableStateFlow(-1)
private val _canGoBack = MutableStateFlow(false)
@@ -57,9 +42,6 @@ class TranslationViewModel(application: Application) : AndroidViewModel(applicat
val historyCursor: StateFlow<Int> get() = _historyCursor
private val translationService = TranslationService(application)
private val apiRepository = ApiRepository(application)
private val _inputText = MutableStateFlow("")
val inputText: StateFlow<String> get() = _inputText

View File

@@ -5,6 +5,8 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.VocabularyItem
import eu.gaudian.translator.model.repository.LanguageConfigRepository
import eu.gaudian.translator.model.repository.LanguageRepository
import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.StringHelper
@@ -54,14 +56,11 @@ data class ExerciseResults(
@HiltViewModel
class VocabularyExerciseViewModel @Inject constructor(
application: Application,
private val vocabularyRepository: VocabularyRepository,
private val languageConfigRepository: LanguageConfigRepository,
private val languageRepository: LanguageRepository
) : AndroidViewModel(application) {
private val vocabularyRepository = VocabularyRepository.getInstance(application)
private val languageConfigViewModel = LanguageConfigViewModel(application)
private val languageViewModel = LanguageViewModel(application)
private val _exerciseState = MutableStateFlow<VocabularyExerciseState?>(null)
val exerciseState: StateFlow<VocabularyExerciseState?> = _exerciseState.asStateFlow()
@@ -232,7 +231,7 @@ class VocabularyExerciseViewModel @Inject constructor(
}
} else {
// Item matches, determine if we need to switch
// Switch if origin is specified and it's not the first language, or if target is specified and it IS the first language
// if origin is specified, and it's not the first language, or if target is specified and it IS the first language
(config.originLanguageId != null && config.originLanguageId != itemToUse.languageFirstId) ||
(config.originLanguageId == null && config.targetLanguageId != null && config.targetLanguageId == itemToUse.languageFirstId)
}
@@ -355,10 +354,11 @@ class VocabularyExerciseViewModel @Inject constructor(
is VocabularyExerciseState.Spelling -> {
val userAnswer = (answer as String).trim()
val languageId = if (state.isSwitched) state.item.languageFirstId else state.item.languageSecondId
val language = languageViewModel.getLanguageById(languageId ?: 0)
val language = languageRepository.getLanguageById(languageId ?: 0)
?: return@launch
// Get articles for the language
val articles = languageConfigViewModel.getArticlesForLanguage(language.code)
val articles = languageConfigRepository.getArticlesForLanguage(language.code)
// Normalize and split possible answers using helper
val normalizedCorrectAnswer = StringHelper.normalizeAnswer(correctAnswer)
@@ -484,6 +484,4 @@ class VocabularyExerciseViewModel @Inject constructor(
}
}
}
}

View File

@@ -11,6 +11,7 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.CardSet
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.VocabularyCategory
@@ -18,11 +19,14 @@ import eu.gaudian.translator.model.VocabularyGrammarDetails
import eu.gaudian.translator.model.VocabularyItem
import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.model.jsonParser
import eu.gaudian.translator.model.repository.LanguageConfigRepository
import eu.gaudian.translator.model.repository.LanguageListType
import eu.gaudian.translator.model.repository.LanguageRepository
import eu.gaudian.translator.model.repository.VocabularyFileSaver
import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.StatusAction
import eu.gaudian.translator.utils.StatusMessageService
import eu.gaudian.translator.utils.StringHelper
import eu.gaudian.translator.utils.VocabularyService
import eu.gaudian.translator.utils.dictionary.DictionaryService
@@ -47,38 +51,20 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import javax.inject.Inject
import kotlin.system.measureTimeMillis
import kotlin.time.ExperimentalTime
/**
* TODO: Convert VocabularyViewModel to HiltViewModel
*
* This ViewModel needs to be converted to use Hilt for dependency injection.
* Currently it manually instantiates dependencies in the constructor and companion object.
*
* Checklist:
* - [ ] Add @HiltViewModel annotation to the class
* - [ ] Change constructor to use @Inject and inject dependencies:
* - ApiManager
* - ApiRepository
* - SettingsRepository
* - ApiLogRepository
* - VocabularyRepository
* - LanguageRepository
* - VocabularyService
* - StatusViewModel (consider if this should be injected or accessed differently)
* - LanguageConfigViewModel (consider if this should be injected or accessed differently)
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
* - VocabularyRepository
* - LanguageRepository
* - VocabularyService
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
* - [ ] Create VocabularyViewModelEntryPoint interface for accessing the singleton instance
* - [ ] Remove manual dependency instantiation from constructor and init block
* - [ ] Update all places where VocabularyViewModel.getInstance() is called to ensure compatibility
* - [ ] Test that the ViewModel works correctly with dependency injection
*/
class VocabularyViewModel(application: Application) : AndroidViewModel(application) {
@HiltViewModel
class VocabularyViewModel @Inject constructor(
application: Application,
private val vocabularyRepository: VocabularyRepository,
private val languageRepository: LanguageRepository,
private val languageConfigRepository: LanguageConfigRepository,
private val vocabularyItemService: VocabularyService,
private val dictionaryService: DictionaryService,
private val statusService: StatusMessageService,
) : AndroidViewModel(application) {
fun extractUniqueWords(text: String): List<String> {
if (text.isBlank()) return emptyList()
val words = text.lowercase()
@@ -95,48 +81,35 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
val src = languageRepository.loadSelectedSourceLanguage().first()
val tgt = languageRepository.loadSelectedTargetLanguage().first()
if (src == null || tgt == null) {
statusViewModel.showErrorMessage("Source and target languages must be selected.")
statusService.showErrorMessage("Source and target languages must be selected.")
return@launch
}
val words = extractUniqueWords(text)
if (words.isEmpty()) {
statusViewModel.showErrorMessage("No words found in the provided text.")
statusService.showErrorMessage("No words found in the provided text.")
return@launch
}
_isGenerating.value = true
statusViewModel.showLoadingMessage("Translating ${words.size} words…")
statusService.showLoadingMessage("Translating ${words.size} words…")
val result = vocabularyItemService.translateWordsBatch(words, src, tgt)
result.onSuccess { items ->
_generatedVocabularyItems.value = items
statusViewModel.cancelLoadingOperation()
statusService.trigger(StatusAction.CancelLoadingOperation)
}.onFailure { ex ->
statusViewModel.showErrorMessage("Failed to translate words: ${ex.message}")
statusService.showErrorMessage("Failed to translate words: ${ex.message}")
}
} catch (e: Exception) {
statusViewModel.showErrorMessage("Unexpected error: ${e.message}")
statusService.showErrorMessage("Unexpected error: ${e.message}")
} finally {
_isGenerating.value = false
}
}
}
companion object {
@Volatile private var INSTANCE: VocabularyViewModel? = null
fun getInstance(application: Application): VocabularyViewModel = INSTANCE ?: synchronized(this) {
INSTANCE ?: VocabularyViewModel(application).also { INSTANCE = it }
}
}
@Suppress("PrivatePropertyName")
private val TAG = "VocabularyViewModel"
val statusViewModel = StatusViewModel.getInstance(application)
val languageConfigViewModel = LanguageConfigViewModel(application)
val vocabularyRepository = VocabularyRepository.getInstance(application)
val languageRepository = LanguageRepository(application)
private val vocabularyItemService = VocabularyService(application)
val vocabularyItems: StateFlow<List<VocabularyItem>> = vocabularyRepository.getAllVocabularyItemsFlow()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
@@ -171,8 +144,6 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
val stageMapping: StateFlow<Map<Int, VocabularyStage>> = vocabularyRepository.loadStageMapping()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap())
val dictionaryService = DictionaryService(application)
private val _cardSet = MutableStateFlow<CardSet?>(null)
val cardSet: StateFlow<CardSet?> = _cardSet.asStateFlow()
@@ -223,7 +194,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
.values // We only need the groups of items, not the keys
.filter { group -> group.size > 1 } // A group with more than one item signifies duplicates
.flatten() // Convert the list of duplicate groups into a a single list of all duplicate items
.flatten() // Convert the list of duplicate groups into a single list of all duplicate items
}
.flowOn(Dispatchers.Default) // Perform this potentially heavy computation on a background thread
.stateIn(
@@ -300,7 +271,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
Log.d(TAG, "Successfully added vocabulary items to category in ${duration}ms")
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error in addVocabularyItemToCategory: ${e.message}")
statusService.showErrorMessage("Error in addVocabularyItemToCategory: ${e.message}")
Log.e(TAG, "Error in addVocabularyItemToCategory: ${e.message}")
}
}
@@ -325,7 +296,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
Log.d(TAG, "Successfully removed vocabulary items from category in ${duration}ms")
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error removing items from category: ${e.message}")
statusService.showErrorMessage("Error removing items from category: ${e.message}")
Log.e(TAG, "Error in removeVocabularyItemsFromCategory: ${e.message}")
}
}
@@ -338,7 +309,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
vocabularyRepository.changeVocabularyItemsStage(vocabularyItems, stage)
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error in addVocabularyItemToStage: ${e.message}")
statusService.showErrorMessage("Error in addVocabularyItemToStage: ${e.message}")
Log.e(TAG, "Error in addVocabularyItemToStage: ${e.message}")
}
}
@@ -356,7 +327,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
"Successfully added ${items.size} new vocabulary items in ${duration}ms"
)
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error adding items: ${e.message}")
statusService.showErrorMessage("Error adding items: ${e.message}")
Log.e(TAG, "Error adding items: ${e.message}")
}
}
@@ -373,7 +344,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
Log.d(TAG, "Successfully deleted vocabulary items in ${duration}ms")
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error deleting items: ${e.message}")
statusService.showErrorMessage("Error deleting items: ${e.message}")
Log.e(TAG, "Error deleting item: ${e.message}")
}
}
@@ -389,7 +360,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
Log.d(TAG, "Successfully edited vocabulary item in ${duration}ms")
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error in editVocabularyItem: ${e.message}")
statusService.showErrorMessage("Error in editVocabularyItem: ${e.message}")
Log.e(TAG, "Error in editVocabularyItem: ${e.message}")
}
}
@@ -445,7 +416,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
// Placeholder: For now, this just deletes the new item.
// A full implementation would merge stages, categories, etc., and update the existing item based on the rules.
viewModelScope.launch {
statusViewModel.showSuccessMessage("Items merged!", 2)
statusService.showSuccessMessage("Items merged!", 2)
deleteVocabularyItemsById(listOf(newItem.id))
}
}
@@ -469,7 +440,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
Log.d(TAG, "deleteData of type $type completed in ${duration}ms")
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error in deleteData: ${e.message}")
statusService.showErrorMessage("Error in deleteData: ${e.message}")
Log.e(TAG, "Error in deleteData: ${e.message}")
}
}
@@ -723,7 +694,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
) {
Log.d(TAG, "Loading card set with languages: $languages, categories: ${categories?.size}, stages: ${stages?.size}")
viewModelScope.launch {
statusViewModel.showLoadingMessage("Loading card set")
statusService.showLoadingMessage("Loading card set")
try {
// Step 1: Wait for vocabulary items to be loaded
@@ -753,7 +724,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
if (stageMappingMap.isEmpty()) {
Log.w(TAG, "loadCardSet: Stage mapping still empty after $maxAttempts attempts")
statusViewModel.showErrorMessage("Failed to initialize stage mappings")
statusService.showErrorMessage("Failed to initialize stage mappings")
_cardSet.value = null
return@launch
} else {
@@ -795,15 +766,15 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
Log.d(TAG, "No items found for the specified filters")
}
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error loading card set: ${e.message}")
statusService.showErrorMessage("Error loading card set: ${e.message}")
_cardSet.value = null
Log.e(TAG, "Error in loadCardSet: ${e.message}")
}
statusViewModel.hideMessageBar()
statusService.hideMessageBar()
if (_cardSet.value == null) {
statusViewModel.cancelAllMessages()
statusViewModel.showErrorMessage("No cards found for the specified filter", 3)
statusService.cancelAllMessages()
statusService.showErrorMessage("No cards found for the specified filter", 3)
}
}
}
@@ -818,7 +789,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
Log.d(TAG, "deleteVocabularyItemsByCategory for ID $categoryID took ${duration}ms")
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error in deleteVocabularyItemsByCategory: ${e.message}")
statusService.showErrorMessage("Error in deleteVocabularyItemsByCategory: ${e.message}")
}
}
}
@@ -858,7 +829,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
fun saveRepositoryState() {
if (!::saveFileLauncher.isInitialized) {
statusViewModel.showErrorMessage("Save File Launcher not initialized.")
statusService.showErrorMessage("Save File Launcher not initialized.")
return
}
val intent = fileSaver.createSaveDocumentIntent(fileSaver.generateFilename())
@@ -867,7 +838,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
fun saveCategory(categoryId: Int) {
if (!::saveFileLauncher.isInitialized) {
statusViewModel.showErrorMessage("Save File Launcher not initialized.")
statusService.showErrorMessage("Save File Launcher not initialized.")
return
}
val filename = fileSaver.generateFilenameForCategory(categoryId)
@@ -897,11 +868,11 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
}
} ?: run {
statusViewModel.showErrorMessage("Error: No URI received from file picker.")
statusService.showErrorMessage("Error: No URI received from file picker.")
Log.e(TAG, "handleSaveResult: No URI received")
}
} else {
statusViewModel.showErrorMessage("File save cancelled or failed.")
statusService.showErrorMessage("File save cancelled or failed.")
Log.d(TAG, "handleSaveResult: Save cancelled or failed")
}
}
@@ -916,10 +887,10 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
vocabularyRepository.introduceVocabularyItems(vocabularyItems)
vocabularyRepository.cleanDuplicates()
}
statusViewModel.showSuccessMessage("Vocabulary items imported successfully.")
statusService.showSuccessMessage("Vocabulary items imported successfully.")
Log.d(TAG, "Vocabulary import process took ${duration}ms")
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error importing vocabulary items: ${e.message}")
statusService.showErrorMessage("Error importing vocabulary items: ${e.message}")
}
}
}
@@ -928,9 +899,9 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
viewModelScope.launch {
try {
vocabularyRepository.wipeRepository()
statusViewModel.showErrorMessage("All repository data deleted.")
statusService.showErrorMessage("All repository data deleted.")
} catch (e: Exception) {
statusViewModel.showErrorMessage("Failed to wipe repository: $e.message}")
statusService.showErrorMessage("Failed to wipe repository: $e.message}")
}
}
}
@@ -1075,7 +1046,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
}
viewModelScope.launch {
statusViewModel.showLoadingMessage("Fetching grammar for ${items.size} items...")
statusService.showLoadingMessage("Fetching grammar for ${items.size} items...")
Log.d(TAG, "Attempting to fetch grammar details for a list of ${items.size} items.")
try {
@@ -1089,8 +1060,8 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
if (StringHelper.isSentenceStrict(item.wordFirst) || StringHelper.isSentenceStrict(item.wordSecond)) {
item // Skip article removal for sentences
} else {
val articlesLangFirst = languagesMap[item.languageFirstId]?.code?.let { languageConfigViewModel.getArticlesForLanguage(it) } ?: emptySet()
val articlesLangSecond = languagesMap[item.languageSecondId]?.code?.let { languageConfigViewModel.getArticlesForLanguage(it) } ?: emptySet()
val articlesLangFirst = languagesMap[item.languageFirstId]?.code?.let { languageConfigRepository.getArticlesForLanguage(it) } ?: emptySet()
val articlesLangSecond = languagesMap[item.languageSecondId]?.code?.let { languageConfigRepository.getArticlesForLanguage(it) } ?: emptySet()
val allArticles = (articlesLangFirst + articlesLangSecond)
@@ -1110,16 +1081,16 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
// 5. Persist the updated items back to the database.
vocabularyRepository.updateVocabularyItems(updatedItems)
statusViewModel.cancelLoadingOperation()
statusViewModel.showSuccessMessage("Grammar details updated!")
statusService.trigger(StatusAction.CancelLoadingOperation)
statusService.showSuccessMessage("Grammar details updated!")
}.onFailure { exception ->
Log.e(TAG, "Failed to fetch grammar details for list: ${exception.message}")
statusViewModel.showErrorMessage("Could not retrieve grammar details.")
statusService.showErrorMessage("Could not retrieve grammar details.")
}
} catch (e: Exception) {
Log.e(TAG, "An unexpected error occurred in fetchAndApplyGrammaticalDetailsForList: ${e.message}")
statusViewModel.showErrorMessage("An unexpected error occurred.")
statusService.showErrorMessage("An unexpected error occurred.")
}
}
}
@@ -1164,7 +1135,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
Log.d(TAG, "removeArticles: No articles found in item ${item.id}, no update needed.")
}
} catch (e: Exception) {
statusViewModel.showErrorMessage("Error removing articles: ${e.message}")
statusService.showErrorMessage("Error removing articles: ${e.message}")
Log.e(TAG, "Error in removeArticles: ${e.message}")
}
}
@@ -1267,14 +1238,14 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
if (itemsToUpdate.isNotEmpty()) {
vocabularyRepository.updateVocabularyItems(itemsToUpdate)
statusViewModel.showSuccessMessage("Language ID updated for ${itemsToUpdate.size} items.")
statusService.showSuccessMessage("Language ID updated for ${itemsToUpdate.size} items.")
Log.d(TAG, "Successfully updated ${itemsToUpdate.size} items.")
} else {
Log.d(TAG, "No items found with language ID $oldId to update.")
}
} catch (e: Exception) {
val errorMessage = "Error replacing language ID: ${e.message}"
statusViewModel.showErrorMessage(errorMessage)
statusService.showErrorMessage(errorMessage)
Log.e(TAG, errorMessage)
}
}

View File

@@ -21,16 +21,6 @@ class SettingsViewModelTest {
androidx.lifecycle.AndroidViewModel::class.java.isAssignableFrom(viewModelClass))
}
@Test
fun `SettingsViewModel companion object has getInstance method`() {
// Given - Get the companion object
val companionObject = SettingsViewModel.Companion::class.java
// Then - Should have getInstance method
assertTrue("Companion should have getInstance method",
companionObject.methods.any { it.name == "getInstance" })
}
@Test
fun `SettingsViewModel has expected constructor parameters`() {
// Given - Get the ViewModel class