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

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

6
.idea/misc.xml generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -58,7 +58,7 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.R
import eu.gaudian.translator.model.Language import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.grammar.DictionaryEntryData 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.model.grammar.UnifiedMorphologyParser
import eu.gaudian.translator.utils.dictionary.LocalDictionaryWordInfo import eu.gaudian.translator.utils.dictionary.LocalDictionaryWordInfo
import eu.gaudian.translator.utils.dictionary.PartOfSpeechTranslator 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.AppIcons
import eu.gaudian.translator.view.composable.AutoResizeSingleLineText import eu.gaudian.translator.view.composable.AutoResizeSingleLineText
import eu.gaudian.translator.viewmodel.DictionaryViewModel import eu.gaudian.translator.viewmodel.DictionaryViewModel
@@ -92,7 +93,8 @@ fun RichLocalEntryDisplay(
expandable: Boolean = true, // Enables whole-entry collapsing expandable: Boolean = true, // Enables whole-entry collapsing
initiallyExpanded: Boolean = true // Default state 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 allConfigs by languageConfigViewModel.configs.collectAsState()
val languageConfig = allConfigs[langCode] val languageConfig = allConfigs[langCode]

View File

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

View File

@@ -22,11 +22,13 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp 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.R
import eu.gaudian.translator.model.Exercise 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.AppButton
import eu.gaudian.translator.view.composable.AppCard import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
@@ -40,7 +42,8 @@ fun ExerciseListScreen(
onLongExerciseClicked: (Exercise) -> Unit, onLongExerciseClicked: (Exercise) -> Unit,
onDeleteClicked: (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() val exercises by exerciseViewModel.exercises.collectAsState()
AppOutlinedCard{ AppOutlinedCard{

View File

@@ -33,11 +33,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.R 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.model.WordOrderQuestion
import eu.gaudian.translator.ui.theme.ThemePreviews import eu.gaudian.translator.ui.theme.ThemePreviews
import eu.gaudian.translator.ui.theme.semanticColors 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.AppButton
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.ComponentDefaults import eu.gaudian.translator.view.composable.ComponentDefaults
@@ -65,9 +67,11 @@ import eu.gaudian.translator.viewmodel.VocabularyViewModel
@Composable @Composable
fun ExerciseSessionScreen( fun ExerciseSessionScreen(
navController: NavController, 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() val sessionState by exerciseViewModel.exerciseSessionState.collectAsState()
var showVocabulary by remember { mutableStateOf(true) } var showVocabulary by remember { mutableStateOf(true) }
@@ -267,7 +271,9 @@ fun ExerciseQuestionScreen(
onCloseClick: () -> Unit, onCloseClick: () -> Unit,
navController: NavController navController: NavController
) { ) {
val exerciseViewModel = viewModel<ExerciseViewModel>() val activity = LocalContext.current.findActivity()
val exerciseViewModel = hiltViewModel<ExerciseViewModel>(viewModelStoreOwner = activity)
Scaffold( Scaffold(
topBar = { topBar = {

View File

@@ -24,11 +24,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.model.Exercise 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.AppAlertDialog
import eu.gaudian.translator.view.composable.AppButton import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
@@ -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. * 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 navController The main NavController to handle navigation to an exercise session.
* @param exerciseViewModel The ViewModel for managing exercise data and state.
*/ */
@Composable @Composable
fun MainExerciseScreen( fun MainExerciseScreen(
navController: NavController, 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() val aiState by exerciseViewModel.aiGenerationState.collectAsState()
var exerciseToDelete by remember { mutableStateOf<Exercise?>(null) } var exerciseToDelete by remember { mutableStateOf<Exercise?>(null) }
var showGenerateDialog by remember { mutableStateOf(false) } var showGenerateDialog by remember { mutableStateOf(false) }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -34,6 +34,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.model.LanguageLevels import eu.gaudian.translator.model.LanguageLevels
import eu.gaudian.translator.model.MyAppLanguageLevel 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.AppDialog
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppScaffold import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.viewmodel.ProgressViewModel
@Composable @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( AppScaffold(
topBar = { topBar = {
AppTopAppBar( AppTopAppBar(
@@ -368,5 +380,5 @@ private fun LevelDetailDialog(level: MyAppLanguageLevel, onDismiss: () -> Unit)
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun LanguageProgressScreenPreview() { fun LanguageProgressScreenPreview() {
LanguageProgressScreen(wordsLearned = 50000, navController = NavController(androidx.compose.ui.platform.LocalContext.current)) LanguageProgressScreen(navController = NavController(LocalContext.current))
} }

View File

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

View File

@@ -30,11 +30,14 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppButton import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppSlider import eu.gaudian.translator.view.composable.AppSlider
@@ -45,14 +48,16 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun NoGrammarItemsScreen( fun NoGrammarItemsScreen(
navController: NavController, 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 itemsWithoutGrammar by vocabularyViewModel.allItemsWithoutGrammar.collectAsState()
val isGenerating by vocabularyViewModel.isGenerating.collectAsState() val isGenerating by vocabularyViewModel.isGenerating.collectAsState()
var showFetchGrammarDialog by remember { mutableStateOf(false) } var showFetchGrammarDialog by remember { mutableStateOf(false) }
@Suppress("UnusedVariable") val onClose = { navController.popBackStack() } @Suppress("UnusedVariable", "unused", "HardCodedStringLiteral") val onClose = { navController.popBackStack() }
if (itemsWithoutGrammar.isEmpty() && !isGenerating) { if (itemsWithoutGrammar.isEmpty() && !isGenerating) {
Column( Column(

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
package eu.gaudian.translator.view.vocabulary package eu.gaudian.translator.view.vocabulary
import android.app.Application
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
@@ -79,9 +78,9 @@ fun StartScreen(
selectedTargetLanguage: Language?, selectedTargetLanguage: Language?,
onTargetLanguageChanged: (Language?) -> Unit, 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 dueTodayItems by vocabularyViewModel.dueTodayItems.collectAsState(initial = emptyList())
val allItems = cardSet?.cards ?: emptyList() val allItems = cardSet?.cards ?: emptyList()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,11 +29,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.blur
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp 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.R
import eu.gaudian.translator.model.Language import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.grammar.CategoryConfig 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.LanguageConfig
import eu.gaudian.translator.model.grammar.formatGrammarDetails import eu.gaudian.translator.model.grammar.formatGrammarDetails
import eu.gaudian.translator.utils.Log 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.AppDialog
import eu.gaudian.translator.view.composable.AppTextField import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.DialogButton import eu.gaudian.translator.view.composable.DialogButton
@@ -54,8 +56,9 @@ internal fun GrammarDetailsText(
isRevealed: Boolean isRevealed: Boolean
) { ) {
Log.d("GrammarComponents", "GrammarDetailsText called with details: $details, language: $language, isRevealed: $isRevealed") 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 allConfigs by configViewModel.configs.collectAsState()
val languageConfig = language?.code?.let { allConfigs[it] } val languageConfig = language?.code?.let { allConfigs[it] }
val categoryConfig = details?.category?.let { languageConfig?.categories?.get(it) } val categoryConfig = details?.category?.let { languageConfig?.categories?.get(it) }
@@ -88,8 +91,9 @@ internal fun GrammarEditDialog(
onSave: (GrammaticalFeature?) -> Unit onSave: (GrammaticalFeature?) -> Unit
) { ) {
Log.d("GrammarComponents", "GrammarEditDialog called with feature: $feature, language: $language") 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 allConfigs by configViewModel.configs.collectAsState()
val languageConfig = allConfigs[language.code] val languageConfig = allConfigs[language.code]

View File

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

View File

@@ -35,14 +35,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import 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.R
import eu.gaudian.translator.model.TagCategory import eu.gaudian.translator.model.TagCategory
import eu.gaudian.translator.model.VocabularyCategory import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.ui.theme.ThemePreviews import eu.gaudian.translator.ui.theme.ThemePreviews
import eu.gaudian.translator.ui.theme.semanticColors 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.AppIcons
import eu.gaudian.translator.view.composable.SecondaryButton import eu.gaudian.translator.view.composable.SecondaryButton
import eu.gaudian.translator.viewmodel.ProgressViewModel import eu.gaudian.translator.viewmodel.ProgressViewModel
@@ -52,7 +55,9 @@ fun CategoryProgressWidget(
onCategoryClicked: (VocabularyCategory?) -> Unit, onCategoryClicked: (VocabularyCategory?) -> Unit,
onViewAllClicked: () -> 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 categoryProgressList by viewModel.categoryProgressList.collectAsState(initial = emptyList())
val selectedCategories by viewModel.selectedCategories.collectAsState(initial = emptySet()) val selectedCategories by viewModel.selectedCategories.collectAsState(initial = emptySet())

View File

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

View File

@@ -5,8 +5,8 @@ package eu.gaudian.translator.viewmodel
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.VocabularyCategory import eu.gaudian.translator.model.VocabularyCategory
//import eu.gaudian.translator.model.VocabularyDictionary
import eu.gaudian.translator.model.repository.VocabularyRepository import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.Log import eu.gaudian.translator.utils.Log
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -18,35 +18,13 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject
/** @HiltViewModel
* TODO: Convert CategoryViewModel to HiltViewModel class CategoryViewModel @Inject constructor(
* application: Application,
* This ViewModel needs to be converted to use Hilt for dependency injection. private val repository: VocabularyRepository
* Currently it manually instantiates dependencies and uses a singleton pattern. ) : AndroidViewModel(application) {
*
* 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)
val categories: StateFlow<List<VocabularyCategory>> = repository.getAllCategoriesFlow() val categories: StateFlow<List<VocabularyCategory>> = repository.getAllCategoriesFlow()
.stateIn( .stateIn(
@@ -146,5 +124,4 @@ class CategoryViewModel(application: Application) : AndroidViewModel(application
_showEditCategoryDialog.value = show _showEditCategoryDialog.value = show
categoryToEdit = categoryId categoryToEdit = categoryId
} }
}
}

View File

@@ -13,35 +13,22 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.Language import eu.gaudian.translator.model.Language
import eu.gaudian.translator.utils.CorrectionService import eu.gaudian.translator.utils.CorrectionService
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
/** class CorrectionViewModel @Inject constructor(
* TODO: Convert CorrectionViewModel to HiltViewModel application: Application,
* private val correctionService: CorrectionService
* This ViewModel needs to be converted to use Hilt for dependency injection. ) : AndroidViewModel(application) {
* 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) {
enum class Tone { NONE, FORMAL, CASUAL, COLLOQUIAL, POLITE, PROFESSIONAL, FRIENDLY, ACADEMIC, CREATIVE } enum class Tone { NONE, FORMAL, CASUAL, COLLOQUIAL, POLITE, PROFESSIONAL, FRIENDLY, ACADEMIC, CREATIVE }
val correctionService = CorrectionService(application.applicationContext)
private val _textFieldValue = MutableStateFlow(TextFieldValue("")) private val _textFieldValue = MutableStateFlow(TextFieldValue(""))
val textFieldValue = _textFieldValue.asStateFlow() val textFieldValue = _textFieldValue.asStateFlow()
@@ -150,4 +137,4 @@ class CorrectionViewModel(application: Application) : AndroidViewModel(applicati
} }
} }
} }
} }

View File

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

View File

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

View File

@@ -29,14 +29,12 @@ import javax.inject.Inject
*/ */
@HiltViewModel @HiltViewModel
class LanguageViewModel @Inject constructor( class LanguageViewModel @Inject constructor(
application: Application application: Application,
private val languageRepository: LanguageRepository
) : AndroidViewModel(application) { ) : AndroidViewModel(application) {
val languageRepository = LanguageRepository(application)
private val languageSwitchMutex = Mutex() private val languageSwitchMutex = Mutex()
// Enabled languages (visible across the app) // Enabled languages (visible across the app)
val allLanguages: StateFlow<List<Language>> = languageRepository.loadLanguages(LanguageListType.ALL) val allLanguages: StateFlow<List<Language>> = languageRepository.loadLanguages(LanguageListType.ALL)
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
@@ -68,8 +66,6 @@ class LanguageViewModel @Inject constructor(
suspend fun getLanguageById(id: Int): Language { suspend fun getLanguageById(id: Int): Language {
val languages = allLanguages.first { it.isNotEmpty() } val languages = allLanguages.first { it.isNotEmpty() }
val language = languages.find { it.nameResId == id } val language = languages.find { it.nameResId == id }
@@ -99,13 +95,13 @@ class LanguageViewModel @Inject constructor(
fun switchLanguages() { fun switchLanguages() {
viewModelScope.launch { viewModelScope.launch {
languageSwitchMutex.withLock { languageSwitchMutex.withLock {
val source = selectedSourceLanguage.value val source = selectedSourceLanguage.value
val target = selectedTargetLanguage.value val target = selectedTargetLanguage.value
languageRepository.saveSelectedSourceLanguage(target) languageRepository.saveSelectedSourceLanguage(target)
languageRepository.saveSelectedTargetLanguage(source) languageRepository.saveSelectedTargetLanguage(source)
}
} }
} }
}
fun setSelectedSourceLanguage(languageId: Int?) { fun setSelectedSourceLanguage(languageId: Int?) {
viewModelScope.launch { viewModelScope.launch {
@@ -193,4 +189,4 @@ class LanguageViewModel @Inject constructor(
languageRepository.editCustomLanguage(languageId, newName, newCode, newRegion) languageRepository.editCustomLanguage(languageId, newName, newCode, newRegion)
} }
} }
} }

View File

@@ -2,8 +2,8 @@ package eu.gaudian.translator.viewmodel
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.application
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.VocabularyCategory import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.model.VocabularyStage import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.model.repository.SettingsRepository import eu.gaudian.translator.model.repository.SettingsRepository
@@ -26,6 +26,7 @@ import kotlinx.datetime.minus
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import java.text.DateFormatSymbols import java.text.DateFormatSymbols
import java.util.Locale import java.util.Locale
import javax.inject.Inject
data class CategoryProgress( data class CategoryProgress(
@@ -53,38 +54,13 @@ data class StageStats(
val itemCount: Int val itemCount: Int
) )
@Suppress("SameParameterValue")
/** @HiltViewModel
* TODO: Convert ProgressViewModel to HiltViewModel class ProgressViewModel @Inject constructor(
* application: Application,
* This ViewModel needs to be converted to use Hilt for dependency injection. private val vocabularyRepository: VocabularyRepository,
* Currently it manually instantiates dependencies and uses a singleton pattern. private val settingsRepository: SettingsRepository
* ) : AndroidViewModel(application) {
* 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)
// Single-flight + coalescing scheduler for refresh() // Single-flight + coalescing scheduler for refresh()
private val refreshMutex = Mutex() private val refreshMutex = Mutex()
@@ -204,7 +180,6 @@ class ProgressViewModel(application: Application) : AndroidViewModel(application
} }
suspend fun getWeeklyActivityStats(): List<WeeklyActivityStat> { suspend fun getWeeklyActivityStats(): List<WeeklyActivityStat> {
val vocabularyRepository = VocabularyRepository.getInstance(application)
val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
// 1. Create a list of the last 7 dates in chronological order. // 1. Create a list of the last 7 dates in chronological order.
@@ -314,4 +289,4 @@ class ProgressViewModel(application: Application) : AndroidViewModel(application
} }
} }
} }

View File

@@ -5,6 +5,7 @@ package eu.gaudian.translator.viewmodel
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.utils.Log import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.StatusAction import eu.gaudian.translator.utils.StatusAction
import eu.gaudian.translator.utils.StatusMessageService import eu.gaudian.translator.utils.StatusMessageService
@@ -17,6 +18,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.LinkedList import java.util.LinkedList
import java.util.Queue import java.util.Queue
import javax.inject.Inject
enum class MessageAction { enum class MessageAction {
NAVIGATE_TO_API_KEYS NAVIGATE_TO_API_KEYS
@@ -43,31 +45,11 @@ enum class MessageDisplayType(val priority: Int) {
ACTIONABLE_ERROR(5) ACTIONABLE_ERROR(5)
} }
/** @HiltViewModel
* TODO: Convert StatusViewModel to HiltViewModel class StatusViewModel @Inject constructor(
* application: Application,
* This ViewModel needs to be converted to use Hilt for dependency injection. private val statusMessageService: StatusMessageService
* Currently it manually instantiates dependencies and uses a singleton pattern. ) : AndroidViewModel(application) {
*
* 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 }
}
}
private val messageQueue: Queue<Pair<String, MessageDisplayType>> = LinkedList() private val messageQueue: Queue<Pair<String, MessageDisplayType>> = LinkedList()
private var messageDisplayJob: Job? = null private var messageDisplayJob: Job? = null
@@ -79,7 +61,7 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
init { init {
viewModelScope.launch { viewModelScope.launch {
StatusMessageService.actions.collect { action -> statusMessageService.actions.collect { action ->
handleAction(action) handleAction(action)
} }
} }
@@ -100,12 +82,10 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
} }
fun showApiKeyMissingMessage() = viewModelScope.launch { fun showApiKeyMissingMessage() = viewModelScope.launch {
StatusMessageService.trigger( statusMessageService.showActionableMessage(
StatusAction.ShowActionableMessage( text = "API Key is missing or invalid.",
text = "API Key is missing or invalid.", type = MessageDisplayType.ACTIONABLE_ERROR,
type = MessageDisplayType.ACTIONABLE_ERROR, action = MessageAction.NAVIGATE_TO_API_KEYS
action = MessageAction.NAVIGATE_TO_API_KEYS
)
) )
} }
@@ -120,43 +100,43 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
} }
fun showPermanentMessage(message: String, type: MessageDisplayType) = viewModelScope.launch { fun showPermanentMessage(message: String, type: MessageDisplayType) = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.ShowPermanentMessage(message, type)) statusMessageService.showPermanentMessage(message, type)
} }
fun cancelPermanentMessage() = viewModelScope.launch { fun cancelPermanentMessage() = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.CancelPermanentMessage) statusMessageService.cancelPermanentMessage()
} }
fun performLoadingOperation(block: suspend () -> Unit) = viewModelScope.launch { fun performLoadingOperation(block: suspend () -> Unit) = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.PerformLoadingOperation(block)) statusMessageService.trigger(StatusAction.PerformLoadingOperation(block))
} }
fun cancelLoadingOperation() = viewModelScope.launch { fun cancelLoadingOperation() = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.CancelLoadingOperation) statusMessageService.trigger(StatusAction.CancelLoadingOperation)
} }
fun showInfoMessage(message: String, timeoutInSeconds: Int = 3) = viewModelScope.launch { 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 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 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 { 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 { fun hideMessageBar() = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.HideMessageBar) statusMessageService.hideMessageBar()
} }
fun cancelAllMessages() = viewModelScope.launch { fun cancelAllMessages() = viewModelScope.launch {
StatusMessageService.trigger(StatusAction.CancelAllMessages) statusMessageService.cancelAllMessages()
} }
private fun cancelPermanentMessageInternal() { private fun cancelPermanentMessageInternal() {

View File

@@ -5,6 +5,7 @@ package eu.gaudian.translator.viewmodel
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.LanguageModel import eu.gaudian.translator.model.LanguageModel
import eu.gaudian.translator.model.TranslationHistoryItem import eu.gaudian.translator.model.TranslationHistoryItem
import eu.gaudian.translator.model.repository.ApiRepository 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.StateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
/** @HiltViewModel
* TODO: Convert TranslationViewModel to HiltViewModel class TranslationViewModel @Inject constructor(
* application: Application,
* This ViewModel needs to be converted to use Hilt for dependency injection. private val translationService: TranslationService,
* Currently it manually instantiates dependencies in the constructor. private val apiRepository: ApiRepository,
* val languageRepository: LanguageRepository
* Checklist: ) : AndroidViewModel(application) {
* - [ ] 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) {
// For back/forward navigation of history in the UI (like editors) // For back/forward navigation of history in the UI (like editors)
val languageRepository = LanguageRepository(application)
private val _historyCursor = MutableStateFlow(-1) private val _historyCursor = MutableStateFlow(-1)
private val _canGoBack = MutableStateFlow(false) private val _canGoBack = MutableStateFlow(false)
@@ -57,9 +42,6 @@ class TranslationViewModel(application: Application) : AndroidViewModel(applicat
val historyCursor: StateFlow<Int> get() = _historyCursor val historyCursor: StateFlow<Int> get() = _historyCursor
private val translationService = TranslationService(application)
private val apiRepository = ApiRepository(application)
private val _inputText = MutableStateFlow("") private val _inputText = MutableStateFlow("")
val inputText: StateFlow<String> get() = _inputText val inputText: StateFlow<String> get() = _inputText
@@ -341,4 +323,4 @@ class TranslationViewModel(application: Application) : AndroidViewModel(applicat
} }
updateBackForwardState() updateBackForwardState()
} }
} }

View File

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

View File

@@ -11,6 +11,7 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.gaudian.translator.model.CardSet import eu.gaudian.translator.model.CardSet
import eu.gaudian.translator.model.Language import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.VocabularyCategory 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.VocabularyItem
import eu.gaudian.translator.model.VocabularyStage import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.model.jsonParser 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.LanguageListType
import eu.gaudian.translator.model.repository.LanguageRepository import eu.gaudian.translator.model.repository.LanguageRepository
import eu.gaudian.translator.model.repository.VocabularyFileSaver import eu.gaudian.translator.model.repository.VocabularyFileSaver
import eu.gaudian.translator.model.repository.VocabularyRepository import eu.gaudian.translator.model.repository.VocabularyRepository
import eu.gaudian.translator.utils.Log 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.StringHelper
import eu.gaudian.translator.utils.VocabularyService import eu.gaudian.translator.utils.VocabularyService
import eu.gaudian.translator.utils.dictionary.DictionaryService import eu.gaudian.translator.utils.dictionary.DictionaryService
@@ -47,38 +51,20 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
/** @HiltViewModel
* TODO: Convert VocabularyViewModel to HiltViewModel class VocabularyViewModel @Inject constructor(
* application: Application,
* This ViewModel needs to be converted to use Hilt for dependency injection. private val vocabularyRepository: VocabularyRepository,
* Currently it manually instantiates dependencies in the constructor and companion object. private val languageRepository: LanguageRepository,
* private val languageConfigRepository: LanguageConfigRepository,
* Checklist: private val vocabularyItemService: VocabularyService,
* - [ ] Add @HiltViewModel annotation to the class private val dictionaryService: DictionaryService,
* - [ ] Change constructor to use @Inject and inject dependencies: private val statusService: StatusMessageService,
* - ApiManager ) : AndroidViewModel(application) {
* - 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) {
fun extractUniqueWords(text: String): List<String> { fun extractUniqueWords(text: String): List<String> {
if (text.isBlank()) return emptyList() if (text.isBlank()) return emptyList()
val words = text.lowercase() val words = text.lowercase()
@@ -95,48 +81,35 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
val src = languageRepository.loadSelectedSourceLanguage().first() val src = languageRepository.loadSelectedSourceLanguage().first()
val tgt = languageRepository.loadSelectedTargetLanguage().first() val tgt = languageRepository.loadSelectedTargetLanguage().first()
if (src == null || tgt == null) { 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 return@launch
} }
val words = extractUniqueWords(text) val words = extractUniqueWords(text)
if (words.isEmpty()) { if (words.isEmpty()) {
statusViewModel.showErrorMessage("No words found in the provided text.") statusService.showErrorMessage("No words found in the provided text.")
return@launch return@launch
} }
_isGenerating.value = true _isGenerating.value = true
statusViewModel.showLoadingMessage("Translating ${words.size} words…") statusService.showLoadingMessage("Translating ${words.size} words…")
val result = vocabularyItemService.translateWordsBatch(words, src, tgt) val result = vocabularyItemService.translateWordsBatch(words, src, tgt)
result.onSuccess { items -> result.onSuccess { items ->
_generatedVocabularyItems.value = items _generatedVocabularyItems.value = items
statusViewModel.cancelLoadingOperation() statusService.trigger(StatusAction.CancelLoadingOperation)
}.onFailure { ex -> }.onFailure { ex ->
statusViewModel.showErrorMessage("Failed to translate words: ${ex.message}") statusService.showErrorMessage("Failed to translate words: ${ex.message}")
} }
} catch (e: Exception) { } catch (e: Exception) {
statusViewModel.showErrorMessage("Unexpected error: ${e.message}") statusService.showErrorMessage("Unexpected error: ${e.message}")
} finally { } finally {
_isGenerating.value = false _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") @Suppress("PrivatePropertyName")
private val TAG = "VocabularyViewModel" private val TAG = "VocabularyViewModel"
val statusViewModel = StatusViewModel.getInstance(application)
val languageConfigViewModel = LanguageConfigViewModel(application)
val vocabularyRepository = VocabularyRepository.getInstance(application)
val languageRepository = LanguageRepository(application)
private val vocabularyItemService = VocabularyService(application)
val vocabularyItems: StateFlow<List<VocabularyItem>> = vocabularyRepository.getAllVocabularyItemsFlow() val vocabularyItems: StateFlow<List<VocabularyItem>> = vocabularyRepository.getAllVocabularyItemsFlow()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
@@ -171,8 +144,6 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
val stageMapping: StateFlow<Map<Int, VocabularyStage>> = vocabularyRepository.loadStageMapping() val stageMapping: StateFlow<Map<Int, VocabularyStage>> = vocabularyRepository.loadStageMapping()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap())
val dictionaryService = DictionaryService(application)
private val _cardSet = MutableStateFlow<CardSet?>(null) private val _cardSet = MutableStateFlow<CardSet?>(null)
val cardSet: StateFlow<CardSet?> = _cardSet.asStateFlow() val cardSet: StateFlow<CardSet?> = _cardSet.asStateFlow()
@@ -223,7 +194,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
} }
.values // We only need the groups of items, not the keys .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 .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 .flowOn(Dispatchers.Default) // Perform this potentially heavy computation on a background thread
.stateIn( .stateIn(
@@ -300,7 +271,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
} }
Log.d(TAG, "Successfully added vocabulary items to category in ${duration}ms") Log.d(TAG, "Successfully added vocabulary items to category in ${duration}ms")
} catch (e: Exception) { } 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}") 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") Log.d(TAG, "Successfully removed vocabulary items from category in ${duration}ms")
} catch (e: Exception) { } 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}") Log.e(TAG, "Error in removeVocabularyItemsFromCategory: ${e.message}")
} }
} }
@@ -338,7 +309,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
vocabularyRepository.changeVocabularyItemsStage(vocabularyItems, stage) vocabularyRepository.changeVocabularyItemsStage(vocabularyItems, stage)
} catch (e: Exception) { } 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}") 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" "Successfully added ${items.size} new vocabulary items in ${duration}ms"
) )
} catch (e: Exception) { } 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}") 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") Log.d(TAG, "Successfully deleted vocabulary items in ${duration}ms")
} catch (e: Exception) { } 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}") 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") Log.d(TAG, "Successfully edited vocabulary item in ${duration}ms")
} catch (e: Exception) { } 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}") 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. // 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. // A full implementation would merge stages, categories, etc., and update the existing item based on the rules.
viewModelScope.launch { viewModelScope.launch {
statusViewModel.showSuccessMessage("Items merged!", 2) statusService.showSuccessMessage("Items merged!", 2)
deleteVocabularyItemsById(listOf(newItem.id)) 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") Log.d(TAG, "deleteData of type $type completed in ${duration}ms")
} catch (e: Exception) { } 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}") 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}") Log.d(TAG, "Loading card set with languages: $languages, categories: ${categories?.size}, stages: ${stages?.size}")
viewModelScope.launch { viewModelScope.launch {
statusViewModel.showLoadingMessage("Loading card set") statusService.showLoadingMessage("Loading card set")
try { try {
// Step 1: Wait for vocabulary items to be loaded // Step 1: Wait for vocabulary items to be loaded
@@ -753,7 +724,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
if (stageMappingMap.isEmpty()) { if (stageMappingMap.isEmpty()) {
Log.w(TAG, "loadCardSet: Stage mapping still empty after $maxAttempts attempts") 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 _cardSet.value = null
return@launch return@launch
} else { } else {
@@ -795,15 +766,15 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
Log.d(TAG, "No items found for the specified filters") Log.d(TAG, "No items found for the specified filters")
} }
} catch (e: Exception) { } catch (e: Exception) {
statusViewModel.showErrorMessage("Error loading card set: ${e.message}") statusService.showErrorMessage("Error loading card set: ${e.message}")
_cardSet.value = null _cardSet.value = null
Log.e(TAG, "Error in loadCardSet: ${e.message}") Log.e(TAG, "Error in loadCardSet: ${e.message}")
} }
statusViewModel.hideMessageBar() statusService.hideMessageBar()
if (_cardSet.value == null) { if (_cardSet.value == null) {
statusViewModel.cancelAllMessages() statusService.cancelAllMessages()
statusViewModel.showErrorMessage("No cards found for the specified filter", 3) 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") Log.d(TAG, "deleteVocabularyItemsByCategory for ID $categoryID took ${duration}ms")
} catch (e: Exception) { } 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() { fun saveRepositoryState() {
if (!::saveFileLauncher.isInitialized) { if (!::saveFileLauncher.isInitialized) {
statusViewModel.showErrorMessage("Save File Launcher not initialized.") statusService.showErrorMessage("Save File Launcher not initialized.")
return return
} }
val intent = fileSaver.createSaveDocumentIntent(fileSaver.generateFilename()) val intent = fileSaver.createSaveDocumentIntent(fileSaver.generateFilename())
@@ -867,7 +838,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
fun saveCategory(categoryId: Int) { fun saveCategory(categoryId: Int) {
if (!::saveFileLauncher.isInitialized) { if (!::saveFileLauncher.isInitialized) {
statusViewModel.showErrorMessage("Save File Launcher not initialized.") statusService.showErrorMessage("Save File Launcher not initialized.")
return return
} }
val filename = fileSaver.generateFilenameForCategory(categoryId) val filename = fileSaver.generateFilenameForCategory(categoryId)
@@ -897,11 +868,11 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
} }
} }
} ?: run { } ?: 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") Log.e(TAG, "handleSaveResult: No URI received")
} }
} else { } else {
statusViewModel.showErrorMessage("File save cancelled or failed.") statusService.showErrorMessage("File save cancelled or failed.")
Log.d(TAG, "handleSaveResult: 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.introduceVocabularyItems(vocabularyItems)
vocabularyRepository.cleanDuplicates() vocabularyRepository.cleanDuplicates()
} }
statusViewModel.showSuccessMessage("Vocabulary items imported successfully.") statusService.showSuccessMessage("Vocabulary items imported successfully.")
Log.d(TAG, "Vocabulary import process took ${duration}ms") Log.d(TAG, "Vocabulary import process took ${duration}ms")
} catch (e: Exception) { } 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 { viewModelScope.launch {
try { try {
vocabularyRepository.wipeRepository() vocabularyRepository.wipeRepository()
statusViewModel.showErrorMessage("All repository data deleted.") statusService.showErrorMessage("All repository data deleted.")
} catch (e: Exception) { } 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 { 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.") Log.d(TAG, "Attempting to fetch grammar details for a list of ${items.size} items.")
try { try {
@@ -1089,8 +1060,8 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
if (StringHelper.isSentenceStrict(item.wordFirst) || StringHelper.isSentenceStrict(item.wordSecond)) { if (StringHelper.isSentenceStrict(item.wordFirst) || StringHelper.isSentenceStrict(item.wordSecond)) {
item // Skip article removal for sentences item // Skip article removal for sentences
} else { } else {
val articlesLangFirst = languagesMap[item.languageFirstId]?.code?.let { languageConfigViewModel.getArticlesForLanguage(it) } ?: emptySet() val articlesLangFirst = languagesMap[item.languageFirstId]?.code?.let { languageConfigRepository.getArticlesForLanguage(it) } ?: emptySet()
val articlesLangSecond = languagesMap[item.languageSecondId]?.code?.let { languageConfigViewModel.getArticlesForLanguage(it) } ?: emptySet() val articlesLangSecond = languagesMap[item.languageSecondId]?.code?.let { languageConfigRepository.getArticlesForLanguage(it) } ?: emptySet()
val allArticles = (articlesLangFirst + articlesLangSecond) val allArticles = (articlesLangFirst + articlesLangSecond)
@@ -1110,16 +1081,16 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
// 5. Persist the updated items back to the database. // 5. Persist the updated items back to the database.
vocabularyRepository.updateVocabularyItems(updatedItems) vocabularyRepository.updateVocabularyItems(updatedItems)
statusViewModel.cancelLoadingOperation() statusService.trigger(StatusAction.CancelLoadingOperation)
statusViewModel.showSuccessMessage("Grammar details updated!") statusService.showSuccessMessage("Grammar details updated!")
}.onFailure { exception -> }.onFailure { exception ->
Log.e(TAG, "Failed to fetch grammar details for list: ${exception.message}") 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) { } catch (e: Exception) {
Log.e(TAG, "An unexpected error occurred in fetchAndApplyGrammaticalDetailsForList: ${e.message}") 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.") Log.d(TAG, "removeArticles: No articles found in item ${item.id}, no update needed.")
} }
} catch (e: Exception) { } 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}") Log.e(TAG, "Error in removeArticles: ${e.message}")
} }
} }
@@ -1267,14 +1238,14 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
if (itemsToUpdate.isNotEmpty()) { if (itemsToUpdate.isNotEmpty()) {
vocabularyRepository.updateVocabularyItems(itemsToUpdate) 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.") Log.d(TAG, "Successfully updated ${itemsToUpdate.size} items.")
} else { } else {
Log.d(TAG, "No items found with language ID $oldId to update.") Log.d(TAG, "No items found with language ID $oldId to update.")
} }
} catch (e: Exception) { } catch (e: Exception) {
val errorMessage = "Error replacing language ID: ${e.message}" val errorMessage = "Error replacing language ID: ${e.message}"
statusViewModel.showErrorMessage(errorMessage) statusService.showErrorMessage(errorMessage)
Log.e(TAG, errorMessage) Log.e(TAG, errorMessage)
} }
} }

View File

@@ -21,16 +21,6 @@ class SettingsViewModelTest {
androidx.lifecycle.AndroidViewModel::class.java.isAssignableFrom(viewModelClass)) 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 @Test
fun `SettingsViewModel has expected constructor parameters`() { fun `SettingsViewModel has expected constructor parameters`() {
// Given - Get the ViewModel class // Given - Get the ViewModel class