From e5c58f58f604a7d0da5d92662f14dafc950e350a Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:05:41 +0100 Subject: [PATCH] Migrate ViewModels to Hilt dependency injection and refactor ViewModel instantiation across the app --- .idea/misc.xml | 6 + .../gaudian/translator/di/RepositoryModule.kt | 60 +++++++- .../repository/LanguageConfigRepository.kt | 85 +++++++++++ .../translator/utils/StatusMessageService.kt | 69 ++++++++- .../translator/utils/VocabularyService.kt | 14 +- .../eu/gaudian/translator/view/Navigation.kt | 32 +--- .../view/dialogs/AddCategoryDialog.kt | 12 +- .../view/dialogs/AddVocabularyDialog.kt | 27 +--- .../view/dialogs/CategoryDropdown.kt | 8 +- .../view/dialogs/CategorySelectionDialog.kt | 3 - .../view/dialogs/DeleteItemsDialog.kt | 13 +- .../view/dialogs/ImportVocabularyDialog.kt | 34 +++-- .../view/dialogs/StartExerciseDialog.kt | 7 +- .../translator/view/dialogs/VocabularyMenu.kt | 12 +- .../view/dialogs/VocabularyReviewScreen.kt | 10 +- .../view/dictionary/DictionaryResultScreen.kt | 9 +- .../view/dictionary/DictionaryScreen.kt | 14 +- .../view/dictionary/EtymologyScreen.kt | 3 +- .../dictionary/LocalWordEntryComponents.kt | 6 +- .../view/dictionary/MainDictionaryScreen.kt | 13 +- .../view/exercises/ExerciseListScreen.kt | 7 +- .../view/exercises/ExerciseSessionScreen.kt | 14 +- .../view/exercises/MainExerciseScreen.kt | 7 +- .../view/exercises/YouTubeBrowserScreen.kt | 8 +- .../view/exercises/YouTubeExerciseScreen.kt | 7 +- .../translator/view/settings/AboutScreen.kt | 4 +- .../VocabularyProgressOptionsScreen.kt | 3 +- .../VocabularyRepositoryOptionsScreen.kt | 12 +- .../view/translation/MainTranslationScreen.kt | 15 -- .../view/translation/TranlsationScreen.kt | 8 +- .../view/vocabulary/CategoryDetailScreen.kt | 8 +- .../view/vocabulary/CategoryListScreen.kt | 12 +- .../view/vocabulary/DashboardContent.kt | 7 +- .../view/vocabulary/LanguageProgressScreen.kt | 16 +- .../view/vocabulary/MainVocabularyScreen.kt | 13 +- .../view/vocabulary/NoGrammarItemsScreen.kt | 9 +- .../view/vocabulary/NoVocabularyScreen.kt | 13 +- .../view/vocabulary/StageDetailScreen.kt | 9 +- .../translator/view/vocabulary/StartScreen.kt | 7 +- .../view/vocabulary/VocabularyCardHost.kt | 11 +- .../view/vocabulary/VocabularyExercise.kt | 7 +- .../VocabularyExerciseHostScreen.kt | 9 +- .../vocabulary/VocabularyHeatMapScreen.kt | 8 +- .../view/vocabulary/VocabularyListScreen.kt | 14 +- .../vocabulary/VocabularySortingScreen.kt | 27 ++-- .../card/AdditionalContentBottomSheet.kt | 11 +- .../view/vocabulary/card/GrammarComponents.kt | 14 +- .../view/vocabulary/card/VocabularyCard.kt | 10 +- .../view/vocabulary/widgets/CategoryWidget.kt | 7 +- .../view/vocabulary/widgets/StatusWidget.kt | 9 +- .../translator/viewmodel/CategoryViewModel.kt | 39 +---- .../viewmodel/CorrectionViewModel.kt | 29 +--- .../translator/viewmodel/ExerciseViewModel.kt | 76 +++------- .../viewmodel/LanguageConfigViewModel.kt | 80 ++-------- .../translator/viewmodel/LanguageViewModel.kt | 20 +-- .../translator/viewmodel/ProgressViewModel.kt | 45 ++---- .../translator/viewmodel/StatusViewModel.kt | 64 +++----- .../viewmodel/TranslationViewModel.kt | 38 ++--- .../viewmodel/VocabularyExerciseViewModel.kt | 22 ++- .../viewmodel/VocabularyViewModel.kt | 143 +++++++----------- .../viewmodel/SettingsViewModelTest.kt | 10 -- 61 files changed, 618 insertions(+), 691 deletions(-) create mode 100644 app/src/main/java/eu/gaudian/translator/model/repository/LanguageConfigRepository.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 11b06af..fa55603 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,10 @@ + + + + + + diff --git a/app/src/main/java/eu/gaudian/translator/di/RepositoryModule.kt b/app/src/main/java/eu/gaudian/translator/di/RepositoryModule.kt index bd0a57c..bc22f88 100644 --- a/app/src/main/java/eu/gaudian/translator/di/RepositoryModule.kt +++ b/app/src/main/java/eu/gaudian/translator/di/RepositoryModule.kt @@ -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 } -} \ No newline at end of file + + @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 + } +} diff --git a/app/src/main/java/eu/gaudian/translator/model/repository/LanguageConfigRepository.kt b/app/src/main/java/eu/gaudian/translator/model/repository/LanguageConfigRepository.kt new file mode 100644 index 0000000..0308d07 --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/model/repository/LanguageConfigRepository.kt @@ -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>(emptyMap()) + val configs: StateFlow> = _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() + + for (fileName in configFiles) { + if (fileName.endsWith(".json")) { + val jsonString = context.assets.open("language_configs/$fileName") + .bufferedReader() + .use { it.readText() } + + val config = jsonParser.decodeFromString(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 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 { + return try { + val config = _configs.value[langCode] + config?.articles?.toSet() ?: emptySet() + } catch (e: Exception) { + Log.e("Error retrieving articles for '$langCode': ${e.message}") + emptySet() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/utils/StatusMessageService.kt b/app/src/main/java/eu/gaudian/translator/utils/StatusMessageService.kt index f8ce685..8fa8d14 100644 --- a/app/src/main/java/eu/gaudian/translator/utils/StatusMessageService.kt +++ b/app/src/main/java/eu/gaudian/translator/utils/StatusMessageService.kt @@ -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)) + } + } } \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/utils/VocabularyService.kt b/app/src/main/java/eu/gaudian/translator/utils/VocabularyService.kt index e6b49b3..076acdd 100644 --- a/app/src/main/java/eu/gaudian/translator/utils/VocabularyService.kt +++ b/app/src/main/java/eu/gaudian/translator/utils/VocabularyService.kt @@ -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 = withContext(Dispatchers.IO) { @@ -269,7 +267,7 @@ class VocabularyService(context: Context) { val wordsWithCategoryOnly = mutableListOf() 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)) { diff --git a/app/src/main/java/eu/gaudian/translator/view/Navigation.kt b/app/src/main/java/eu/gaudian/translator/view/Navigation.kt index 15a4f35..019fe95 100644 --- a/app/src/main/java/eu/gaudian/translator/view/Navigation.kt +++ b/app/src/main/java/eu/gaudian/translator/view/Navigation.kt @@ -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 ) } } diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/AddCategoryDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/AddCategoryDialog.kt index 9e837d8..ac5d079 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/AddCategoryDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/AddCategoryDialog.kt @@ -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>(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?, onPairSelected: (Pair) -> Unit ) { val activity = LocalContext.current.findActivity() + val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity) val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) var allDictionaries by remember { mutableStateOf>>(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 = {} ) diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/AddVocabularyDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/AddVocabularyDialog.kt index eec1ba8..ebddd0f 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/AddVocabularyDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/AddVocabularyDialog.kt @@ -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(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 = {}) } \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/CategoryDropdown.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/CategoryDropdown.kt index a9a144b..26c43cc 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/CategoryDropdown.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/CategoryDropdown.kt @@ -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) -> 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() else categories @@ -229,7 +232,6 @@ fun CategoryDropdown( @Composable fun CategoryDropdownPreview() { CategoryDropdown( - categoryViewModel = viewModel(), onCategorySelected = {} ) } \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/CategorySelectionDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/CategorySelectionDialog.kt index 07f051e..1a95dc5 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/CategorySelectionDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/CategorySelectionDialog.kt @@ -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) -> Unit, onDismissRequest: () -> Unit, ) { @@ -35,7 +33,6 @@ fun CategorySelectionDialog( CategoryDropdown( - categoryViewModel = viewModel, onCategorySelected = { categories -> selectedCategory = categories }, diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/DeleteItemsDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/DeleteItemsDialog.kt index 8ce88c4..fff02d9 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/DeleteItemsDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/DeleteItemsDialog.kt @@ -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() } ) { diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt index 4fe19d0..1e11a87 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt @@ -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() @@ -205,4 +203,20 @@ fun ImportDialogContent( } } ) -} \ No newline at end of file +} + +@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" + ) +} diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/StartExerciseDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/StartExerciseDialog.kt index 35cb3e6..175c794 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/StartExerciseDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/StartExerciseDialog.kt @@ -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, @@ -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>(emptyList()) } var languages by remember { mutableStateOf>(emptyList()) } @@ -85,7 +81,6 @@ fun StartExerciseDialog( languages ) CategoryDropdown( - categoryViewModel = categoryViewModel, onCategorySelected = { categories -> selectedCategories = categories.filterIsInstance() }, diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyMenu.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyMenu.kt index da51dd5..2147682 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyMenu.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyMenu.kt @@ -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 } ) } diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt index abe4b8f..7624242 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt @@ -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, List) -> Unit, onCancel: () -> Unit ) { + val activity = LocalContext.current.findActivity() + val vocabularyViewModel : VocabularyViewModel = hiltViewModel(activity) val generatedItems: List by vocabularyViewModel.generatedVocabularyItems.collectAsState() val selectedItems = remember { mutableStateListOf() } val duplicates = remember { mutableStateListOf() } @@ -129,7 +128,6 @@ fun VocabularyReviewScreen( modifier = Modifier.padding(8.dp) ) CategoryDropdown( - categoryViewModel = categoryViewModel, onCategorySelected = { categories: List -> selectedCategoryId = categories.filterNotNull().map { it.id } }, diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryResultScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryResultScreen.kt index bcbd479..69959ce 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryResultScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryResultScreen.kt @@ -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") diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryScreen.kt index 452eeab..6c9d9fa 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryScreen.kt @@ -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 = {} ) } \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/EtymologyScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/EtymologyScreen.kt index a0bf8da..b48ca4f 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/EtymologyScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dictionary/EtymologyScreen.kt @@ -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(), diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/LocalWordEntryComponents.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/LocalWordEntryComponents.kt index 95b5d08..3beb5fb 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/LocalWordEntryComponents.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dictionary/LocalWordEntryComponents.kt @@ -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] diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt index ba02cff..163bf76 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt @@ -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) diff --git a/app/src/main/java/eu/gaudian/translator/view/exercises/ExerciseListScreen.kt b/app/src/main/java/eu/gaudian/translator/view/exercises/ExerciseListScreen.kt index b48ef39..067dfd5 100644 --- a/app/src/main/java/eu/gaudian/translator/view/exercises/ExerciseListScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/exercises/ExerciseListScreen.kt @@ -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{ diff --git a/app/src/main/java/eu/gaudian/translator/view/exercises/ExerciseSessionScreen.kt b/app/src/main/java/eu/gaudian/translator/view/exercises/ExerciseSessionScreen.kt index 8628cbe..f97955d 100644 --- a/app/src/main/java/eu/gaudian/translator/view/exercises/ExerciseSessionScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/exercises/ExerciseSessionScreen.kt @@ -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() + val activity = LocalContext.current.findActivity() + val exerciseViewModel = hiltViewModel(viewModelStoreOwner = activity) + Scaffold( topBar = { diff --git a/app/src/main/java/eu/gaudian/translator/view/exercises/MainExerciseScreen.kt b/app/src/main/java/eu/gaudian/translator/view/exercises/MainExerciseScreen.kt index e7f8b6a..0bef304 100644 --- a/app/src/main/java/eu/gaudian/translator/view/exercises/MainExerciseScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/exercises/MainExerciseScreen.kt @@ -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(null) } var showGenerateDialog by remember { mutableStateOf(false) } diff --git a/app/src/main/java/eu/gaudian/translator/view/exercises/YouTubeBrowserScreen.kt b/app/src/main/java/eu/gaudian/translator/view/exercises/YouTubeBrowserScreen.kt index 8d32325..fbaa1ef 100644 --- a/app/src/main/java/eu/gaudian/translator/view/exercises/YouTubeBrowserScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/exercises/YouTubeBrowserScreen.kt @@ -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(null) } var showLanguageDialog by remember { mutableStateOf(false) } var showCustomDialog by remember { mutableStateOf(false) } diff --git a/app/src/main/java/eu/gaudian/translator/view/exercises/YouTubeExerciseScreen.kt b/app/src/main/java/eu/gaudian/translator/view/exercises/YouTubeExerciseScreen.kt index af067a9..e24ff71 100644 --- a/app/src/main/java/eu/gaudian/translator/view/exercises/YouTubeExerciseScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/exercises/YouTubeExerciseScreen.kt @@ -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() diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/AboutScreen.kt b/app/src/main/java/eu/gaudian/translator/view/settings/AboutScreen.kt index 244e9a4..2a2b140 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/AboutScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/AboutScreen.kt @@ -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) diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyProgressOptionsScreen.kt b/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyProgressOptionsScreen.kt index e66baa1..09efe94 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyProgressOptionsScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyProgressOptionsScreen.kt @@ -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( diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyRepositoryOptionsScreen.kt b/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyRepositoryOptionsScreen.kt index e499bc5..39b91ac 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyRepositoryOptionsScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyRepositoryOptionsScreen.kt @@ -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>>(emptyList()) } diff --git a/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt b/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt index 5c45ae6..c81df6a 100644 --- a/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt @@ -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 }, diff --git a/app/src/main/java/eu/gaudian/translator/view/translation/TranlsationScreen.kt b/app/src/main/java/eu/gaudian/translator/view/translation/TranlsationScreen.kt index 73da6e3..c8a5208 100644 --- a/app/src/main/java/eu/gaudian/translator/view/translation/TranlsationScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/translation/TranlsationScreen.kt @@ -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(), ) } \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/CategoryDetailScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/CategoryDetailScreen.kt index a944ddf..0844f85 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/CategoryDetailScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/CategoryDetailScreen.kt @@ -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 ) } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/CategoryListScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/CategoryListScreen.kt index f65c38c..9eb54fd 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/CategoryListScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/CategoryListScreen.kt @@ -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, ) } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/DashboardContent.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/DashboardContent.kt index 9b2f68f..ce92ae0 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/DashboardContent.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/DashboardContent.kt @@ -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(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, diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/LanguageProgressScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/LanguageProgressScreen.kt index 5615bd6..19d3b86 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/LanguageProgressScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/LanguageProgressScreen.kt @@ -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)) } \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/MainVocabularyScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/MainVocabularyScreen.kt index a8cea1c..f5b4852 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/MainVocabularyScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/MainVocabularyScreen.kt @@ -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 diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NoGrammarItemsScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NoGrammarItemsScreen.kt index bbaa3c3..88250d2 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NoGrammarItemsScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NoGrammarItemsScreen.kt @@ -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( diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NoVocabularyScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NoVocabularyScreen.kt index 1100ac4..1ef5276 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NoVocabularyScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NoVocabularyScreen.kt @@ -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 } ) diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/StageDetailScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/StageDetailScreen.kt index fb3db6f..bfb9e68 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/StageDetailScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/StageDetailScreen.kt @@ -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(viewModelStoreOwner = activity) + val stageMapping by viewModel.stageMapping.collectAsState() val dueTodayItems by viewModel.dueTodayItems.collectAsState() diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/StartScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/StartScreen.kt index a3d7f40..76cad50 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/StartScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/StartScreen.kt @@ -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() diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyCardHost.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyCardHost.kt index b7fa644..f632eec 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyCardHost.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyCardHost.kt @@ -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 } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExercise.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExercise.kt index 2c52196..caeb696 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExercise.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExercise.kt @@ -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 ) } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExerciseHostScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExerciseHostScreen.kt index eb012e5..7b1dc13 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExerciseHostScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExerciseHostScreen.kt @@ -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() diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyHeatMapScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyHeatMapScreen.kt index 30fbdfc..f85c7b6 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyHeatMapScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyHeatMapScreen.kt @@ -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, 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 = {}, diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt index fa5260f..79e84a6 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt @@ -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() ) } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/AdditionalContentBottomSheet.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/AdditionalContentBottomSheet.kt index 8356cb8..479e4c4 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/AdditionalContentBottomSheet.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/AdditionalContentBottomSheet.kt @@ -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?, 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 diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/GrammarComponents.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/GrammarComponents.kt index 023a9b3..5130717 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/GrammarComponents.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/GrammarComponents.kt @@ -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 @@ -54,8 +56,9 @@ internal fun GrammarDetailsText( isRevealed: Boolean ) { 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) } @@ -88,8 +91,9 @@ internal fun GrammarEditDialog( onSave: (GrammaticalFeature?) -> Unit ) { 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] diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/VocabularyCard.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/VocabularyCard.kt index dd71dd0..2c5a002 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/VocabularyCard.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/card/VocabularyCard.kt @@ -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) } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/widgets/CategoryWidget.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/widgets/CategoryWidget.kt index 1235b97..e6d553f 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/widgets/CategoryWidget.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/widgets/CategoryWidget.kt @@ -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()) diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/widgets/StatusWidget.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/widgets/StatusWidget.kt index 15c6ee7..75377f1 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/widgets/StatusWidget.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/widgets/StatusWidget.kt @@ -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() diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/CategoryViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/CategoryViewModel.kt index f1dc2c0..4ce97a6 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/CategoryViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/CategoryViewModel.kt @@ -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> = repository.getAllCategoriesFlow() .stateIn( @@ -146,5 +124,4 @@ class CategoryViewModel(application: Application) : AndroidViewModel(application _showEditCategoryDialog.value = show categoryToEdit = categoryId } - -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/CorrectionViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/CorrectionViewModel.kt index b7c5a33..6c00a52 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/CorrectionViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/CorrectionViewModel.kt @@ -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() @@ -150,4 +137,4 @@ class CorrectionViewModel(application: Application) : AndroidViewModel(applicati } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/ExerciseViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/ExerciseViewModel.kt index 832f2ca..992d911 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/ExerciseViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/ExerciseViewModel.kt @@ -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 } - } - } + + @SuppressLint("StaticFieldLeak") + 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>(emptyList()) val exercises: StateFlow> = _exercises.asStateFlow() @@ -169,7 +135,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application val youTubeExerciseState: StateFlow = _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 = 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( @@ -616,4 +582,4 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application // Reset the exercise state to allow restarting _youTubeExerciseState.value = YouTubeExerciseState.Idle } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/LanguageConfigViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/LanguageConfigViewModel.kt index 82e0514..dbf82ea 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/LanguageConfigViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/LanguageConfigViewModel.kt @@ -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>(emptyMap()) - val configs = _configs.asStateFlow() - - private val jsonParser = Json { ignoreUnknownKeys = true } - - init { - loadAllConfigs() - } + val configs: StateFlow> = languageConfigRepository.configs val allWordClasses: StateFlow> = 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().applicationContext - try { - val configFiles = context.assets.list("language_configs") ?: return@launch - val loadedConfigs = mutableMapOf() - - for (fileName in configFiles) { - if (fileName.endsWith(".json")) { - val jsonString = context.assets.open("language_configs/$fileName") - .bufferedReader() - .use { it.readText() } - - val config = jsonParser.decodeFromString(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 { - 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) } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/LanguageViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/LanguageViewModel.kt index 741af13..7c3c842 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/LanguageViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/LanguageViewModel.kt @@ -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> = 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 } @@ -99,13 +95,13 @@ class LanguageViewModel @Inject constructor( fun switchLanguages() { viewModelScope.launch { languageSwitchMutex.withLock { - val source = selectedSourceLanguage.value - val target = selectedTargetLanguage.value - languageRepository.saveSelectedSourceLanguage(target) - languageRepository.saveSelectedTargetLanguage(source) + val source = selectedSourceLanguage.value + val target = selectedTargetLanguage.value + languageRepository.saveSelectedSourceLanguage(target) + languageRepository.saveSelectedTargetLanguage(source) + } } } - } fun setSelectedSourceLanguage(languageId: Int?) { viewModelScope.launch { @@ -193,4 +189,4 @@ class LanguageViewModel @Inject constructor( languageRepository.editCustomLanguage(languageId, newName, newCode, newRegion) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/ProgressViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/ProgressViewModel.kt index 383009b..68f0bb0 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/ProgressViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/ProgressViewModel.kt @@ -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 { - 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. @@ -314,4 +289,4 @@ class ProgressViewModel(application: Application) : AndroidViewModel(application } } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/StatusViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/StatusViewModel.kt index a150e46..d646c5a 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/StatusViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/StatusViewModel.kt @@ -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> = 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,12 +82,10 @@ class StatusViewModel(application: Application) : AndroidViewModel(application) } fun showApiKeyMissingMessage() = viewModelScope.launch { - StatusMessageService.trigger( - StatusAction.ShowActionableMessage( - text = "API Key is missing or invalid.", - type = MessageDisplayType.ACTIONABLE_ERROR, - action = MessageAction.NAVIGATE_TO_API_KEYS - ) + statusMessageService.showActionableMessage( + text = "API Key is missing or invalid.", + type = MessageDisplayType.ACTIONABLE_ERROR, + action = MessageAction.NAVIGATE_TO_API_KEYS ) } @@ -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() { diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/TranslationViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/TranslationViewModel.kt index 1418593..9c72af1 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/TranslationViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/TranslationViewModel.kt @@ -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 get() = _historyCursor - private val translationService = TranslationService(application) - private val apiRepository = ApiRepository(application) - private val _inputText = MutableStateFlow("") val inputText: StateFlow get() = _inputText @@ -341,4 +323,4 @@ class TranslationViewModel(application: Application) : AndroidViewModel(applicat } updateBackForwardState() } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt index 96d21f4..7e093bd 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt @@ -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(null) val exerciseState: StateFlow = _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( } } } - - -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt index c9ea59d..4be4730 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt @@ -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 { 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> = vocabularyRepository.getAllVocabularyItemsFlow() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) @@ -171,8 +144,6 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati val stageMapping: StateFlow> = vocabularyRepository.loadStageMapping() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap()) - val dictionaryService = DictionaryService(application) - private val _cardSet = MutableStateFlow(null) val cardSet: StateFlow = _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) } } diff --git a/app/src/test/java/eu/gaudian/translator/viewmodel/SettingsViewModelTest.kt b/app/src/test/java/eu/gaudian/translator/viewmodel/SettingsViewModelTest.kt index c68e92a..9d6f1eb 100644 --- a/app/src/test/java/eu/gaudian/translator/viewmodel/SettingsViewModelTest.kt +++ b/app/src/test/java/eu/gaudian/translator/viewmodel/SettingsViewModelTest.kt @@ -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