Migrate ViewModels to Hilt dependency injection and refactor ViewModel instantiation across the app
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("unused", "HardCodedStringLiteral")
|
||||
|
||||
package eu.gaudian.translator.di
|
||||
|
||||
import android.app.Application
|
||||
@@ -11,10 +13,18 @@ import eu.gaudian.translator.model.repository.ApiRepository
|
||||
import eu.gaudian.translator.model.repository.DictionaryFileRepository
|
||||
import eu.gaudian.translator.model.repository.DictionaryLookupRepository
|
||||
import eu.gaudian.translator.model.repository.DictionaryRepository
|
||||
import eu.gaudian.translator.model.repository.ExerciseRepository
|
||||
import eu.gaudian.translator.model.repository.LanguageConfigRepository
|
||||
import eu.gaudian.translator.model.repository.LanguageRepository
|
||||
import eu.gaudian.translator.model.repository.SettingsRepository
|
||||
import eu.gaudian.translator.model.repository.VocabularyRepository
|
||||
import eu.gaudian.translator.utils.CorrectionService
|
||||
import eu.gaudian.translator.utils.ExerciseService
|
||||
import eu.gaudian.translator.utils.StatusMessageService
|
||||
import eu.gaudian.translator.utils.TranslationService
|
||||
import eu.gaudian.translator.utils.VocabularyService
|
||||
import eu.gaudian.translator.utils.YouTubeApiService
|
||||
import eu.gaudian.translator.utils.YouTubeExerciseService
|
||||
import eu.gaudian.translator.utils.dictionary.DictionaryService
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -58,6 +68,12 @@ object RepositoryModule {
|
||||
return LanguageRepository(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideLanguageConfigRepository(application: Application): LanguageConfigRepository {
|
||||
return LanguageConfigRepository(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDictionaryFileRepository(application: Application): DictionaryFileRepository {
|
||||
@@ -87,4 +103,46 @@ object RepositoryModule {
|
||||
fun provideStatusMessageService(): StatusMessageService {
|
||||
return StatusMessageService
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideVocabularyService(application: Application): VocabularyService {
|
||||
return VocabularyService(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideExerciseRepository(application: Application): ExerciseRepository {
|
||||
return ExerciseRepository(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideExerciseService(application: Application): ExerciseService {
|
||||
return ExerciseService(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTranslationService(application: Application): TranslationService {
|
||||
return TranslationService(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCorrectionService(application: Application): CorrectionService {
|
||||
return CorrectionService(application)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYouTubeApiService(): YouTubeApiService {
|
||||
return YouTubeApiService
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYouTubeExerciseService(): YouTubeExerciseService {
|
||||
return YouTubeExerciseService
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,77 @@ object StatusMessageService {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun showSimpleMessage(text: String, type: MessageDisplayType = MessageDisplayType.INFO) {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.ShowMessage(text, type, 5))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun showErrorMessage(text: String, timeoutInSeconds: Int = 5) {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.ShowMessage(
|
||||
text, MessageDisplayType.ERROR,
|
||||
timeoutInSeconds = timeoutInSeconds
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun showLoadingMessage(text: String, timeoutInSeconds: Int = 0) {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.ShowMessage(
|
||||
text, MessageDisplayType.LOADING,
|
||||
timeoutInSeconds = timeoutInSeconds
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fun showInfoMessage(text: String, timeoutInSeconds: Int = 3) {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.ShowMessage(
|
||||
text, MessageDisplayType.INFO,
|
||||
timeoutInSeconds = timeoutInSeconds
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fun showSuccessMessage(text: String, timeoutInSeconds: Int = 3) {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.ShowMessage(
|
||||
text, MessageDisplayType.SUCCESS,
|
||||
timeoutInSeconds = timeoutInSeconds
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fun showPermanentMessage(text: String, type: MessageDisplayType) {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.ShowPermanentMessage(text, type))
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelPermanentMessage() {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.CancelPermanentMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun hideMessageBar() {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.HideMessageBar)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelAllMessages() {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.CancelAllMessages)
|
||||
}
|
||||
}
|
||||
|
||||
fun showActionableMessage(text: String, type: MessageDisplayType, action: MessageAction) {
|
||||
scope.launch {
|
||||
_actions.emit(StatusAction.ShowActionableMessage(text, type, action))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package eu.gaudian.translator.utils
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import eu.gaudian.translator.model.Language
|
||||
import eu.gaudian.translator.model.VocabularyItem
|
||||
@@ -10,9 +9,9 @@ import eu.gaudian.translator.model.communication.ApiManager
|
||||
import eu.gaudian.translator.model.grammar.GrammaticalFeature
|
||||
import eu.gaudian.translator.model.grammar.LanguageConfig
|
||||
import eu.gaudian.translator.model.grammar.VocabularyFeatures
|
||||
import eu.gaudian.translator.model.repository.LanguageConfigRepository
|
||||
import eu.gaudian.translator.model.repository.SettingsRepository
|
||||
import eu.gaudian.translator.utils.StringHelper.isSentenceStrict
|
||||
import eu.gaudian.translator.viewmodel.LanguageConfigViewModel
|
||||
import eu.gaudian.translator.viewmodel.MessageDisplayType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
@@ -28,8 +27,7 @@ class VocabularyService(context: Context) {
|
||||
|
||||
private val apiRequestHandler = ApiRequestHandler(ApiManager(context), context)
|
||||
private val settingsRepository = SettingsRepository(context)
|
||||
private val languageConfigViewModel =
|
||||
LanguageConfigViewModel(context.applicationContext as Application)
|
||||
private val languageConfigRepository = LanguageConfigRepository(context)
|
||||
private val jsonParser = Json { ignoreUnknownKeys = true; isLenient = true }
|
||||
|
||||
suspend fun fetchZipfFrequency(word: String, languageCode: String): Result<Float> = withContext(Dispatchers.IO) {
|
||||
@@ -269,7 +267,7 @@ class VocabularyService(context: Context) {
|
||||
val wordsWithCategoryOnly = mutableListOf<ClassifiedWord>()
|
||||
|
||||
classifiedWords.forEach { word ->
|
||||
val config = languageConfigViewModel.getConfigForLanguage(word.languageCode)
|
||||
val config = languageConfigRepository.getConfigForLanguage(word.languageCode)
|
||||
val categoryHasFields = config?.categories?.get(word.category)?.fields?.isNotEmpty() ?: false
|
||||
if (categoryHasFields) {
|
||||
wordsWithFeaturesToFetch.add(word)
|
||||
@@ -283,7 +281,7 @@ class VocabularyService(context: Context) {
|
||||
val featureResults = groupedByLangAndCategory.map { (groupKey, words) ->
|
||||
async {
|
||||
val (langCode, category) = groupKey
|
||||
val languageConfig = languageConfigViewModel.getConfigForLanguage(langCode)
|
||||
val languageConfig = languageConfigRepository.getConfigForLanguage(langCode)
|
||||
fetchFeaturesForGroup(words, languageConfig, category)
|
||||
}
|
||||
}.awaitAll().filterNotNull().flatten()
|
||||
@@ -329,7 +327,7 @@ class VocabularyService(context: Context) {
|
||||
val wordsToClassify = wordsNeedingApi.map {
|
||||
WordToAnalyze(it.tempId, it.word, languages[it.languageId]?.englishName ?: "")
|
||||
}
|
||||
val languageConfigs = languageConfigViewModel.configs.value
|
||||
val languageConfigs = languageConfigRepository.configs.value
|
||||
val possibleCategories = languageConfigs.values.flatMap { it.categories.keys }.distinct()
|
||||
|
||||
val wordsToClassifyJson = jsonParser.encodeToString(wordsToClassify)
|
||||
@@ -442,7 +440,7 @@ class VocabularyService(context: Context) {
|
||||
)
|
||||
|
||||
var finalWord = classifiedWord.word
|
||||
val languageConfig = languageConfigViewModel.getConfigForLanguage(classifiedWord.languageCode)
|
||||
val languageConfig = languageConfigRepository.getConfigForLanguage(classifiedWord.languageCode)
|
||||
val categoryConfig = languageConfig?.categories?.get(newGrammaticalFeature.category)
|
||||
|
||||
if (!isSentenceStrict(finalWord)) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package eu.gaudian.translator.view
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
@@ -12,9 +11,7 @@ import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.NavType
|
||||
@@ -29,6 +26,7 @@ import eu.gaudian.translator.view.dictionary.EtymologyResultScreen
|
||||
import eu.gaudian.translator.view.dictionary.MainDictionaryScreen
|
||||
import eu.gaudian.translator.view.exercises.ExerciseSessionScreen
|
||||
import eu.gaudian.translator.view.exercises.MainExerciseScreen
|
||||
import eu.gaudian.translator.view.exercises.YouTubeBrowserScreen
|
||||
import eu.gaudian.translator.view.exercises.YouTubeExerciseScreen
|
||||
import eu.gaudian.translator.view.settings.DictionaryOptionsScreen
|
||||
import eu.gaudian.translator.view.settings.SettingsRoutes
|
||||
@@ -46,12 +44,7 @@ import eu.gaudian.translator.view.vocabulary.VocabularyExerciseHostScreen
|
||||
import eu.gaudian.translator.view.vocabulary.VocabularyHeatmapScreen
|
||||
import eu.gaudian.translator.view.vocabulary.VocabularyListScreen
|
||||
import eu.gaudian.translator.view.vocabulary.VocabularySortingScreen
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.ExerciseViewModel
|
||||
import eu.gaudian.translator.viewmodel.ProgressViewModel
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
private const val ANIMATION_DURATION = 200
|
||||
private const val TRANSITION_DURATION = 300
|
||||
|
||||
@Composable
|
||||
@@ -59,8 +52,8 @@ fun AppNavHost(
|
||||
navController: NavHostController,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val exerciseViewModel: ExerciseViewModel = viewModel()
|
||||
val progressViewModel: ProgressViewModel = ProgressViewModel.getInstance(applicationContext as Application)
|
||||
|
||||
|
||||
|
||||
// 1. Define your Main Tab "Leaf" Routes (the actual start destinations of your graphs)
|
||||
val mainTabRoutes = setOf(
|
||||
@@ -134,8 +127,8 @@ fun AppNavHost(
|
||||
// Define all other navigation graphs at the same top level.
|
||||
translationGraph(navController)
|
||||
dictionaryGraph(navController)
|
||||
vocabularyGraph(navController, progressViewModel)
|
||||
exerciseGraph(navController, exerciseViewModel)
|
||||
vocabularyGraph(navController)
|
||||
exerciseGraph(navController)
|
||||
settingsGraph(navController)
|
||||
}
|
||||
}
|
||||
@@ -190,10 +183,8 @@ fun NavGraphBuilder.dictionaryGraph(navController: NavHostController) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(androidx.compose.animation.ExperimentalAnimationApi::class)
|
||||
fun NavGraphBuilder.vocabularyGraph(
|
||||
navController: NavHostController,
|
||||
progressViewModel: ProgressViewModel
|
||||
) {
|
||||
navigation(
|
||||
startDestination = "main_vocabulary",
|
||||
@@ -246,14 +237,12 @@ fun NavGraphBuilder.vocabularyGraph(
|
||||
}
|
||||
composable("language_progress") {
|
||||
LanguageProgressScreen(
|
||||
wordsLearned = progressViewModel.totalWordsCompleted.collectAsState().value,
|
||||
navController = navController
|
||||
)
|
||||
|
||||
}
|
||||
composable("vocabulary_heatmap") {
|
||||
VocabularyHeatmapScreen(
|
||||
viewModel = progressViewModel,
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
@@ -358,8 +347,6 @@ fun NavGraphBuilder.vocabularyGraph(
|
||||
}
|
||||
composable("category_list_screen") {
|
||||
CategoryListScreen(
|
||||
categoryViewModel = CategoryViewModel.getInstance(applicationContext as Application),
|
||||
progressViewModel = ProgressViewModel.getInstance(applicationContext as Application),
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onCategoryClicked = { categoryId ->
|
||||
navController.navigate("category_detail/$categoryId")
|
||||
@@ -392,7 +379,6 @@ fun NavGraphBuilder.vocabularyGraph(
|
||||
@OptIn(androidx.compose.animation.ExperimentalAnimationApi::class)
|
||||
fun NavGraphBuilder.exerciseGraph(
|
||||
navController: NavHostController,
|
||||
exerciseViewModel: ExerciseViewModel
|
||||
) {
|
||||
navigation(
|
||||
startDestination = "main_exercise",
|
||||
@@ -401,25 +387,21 @@ fun NavGraphBuilder.exerciseGraph(
|
||||
composable("main_exercise") {
|
||||
MainExerciseScreen(
|
||||
navController = navController,
|
||||
exerciseViewModel = exerciseViewModel
|
||||
)
|
||||
}
|
||||
composable("exercise_session") {
|
||||
ExerciseSessionScreen(
|
||||
navController = navController,
|
||||
exerciseViewModel = exerciseViewModel
|
||||
)
|
||||
}
|
||||
composable("youtube_exercise") {
|
||||
YouTubeExerciseScreen(
|
||||
navController = navController,
|
||||
exerciseViewModel = exerciseViewModel
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
composable("youtube_browse") {
|
||||
eu.gaudian.translator.view.exercises.YouTubeBrowserScreen(
|
||||
YouTubeBrowserScreen(
|
||||
navController = navController,
|
||||
exerciseViewModel = exerciseViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.Language
|
||||
import eu.gaudian.translator.model.TagCategory
|
||||
@@ -57,10 +56,10 @@ enum class DialogCategoryType { TAG, FILTER }
|
||||
|
||||
@Composable
|
||||
fun AddCategoryDialog(
|
||||
onDismiss: () -> Unit,
|
||||
categoryViewModel: CategoryViewModel
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
var categoryName by remember { mutableStateOf("") }
|
||||
var selectedLanguages by remember { mutableStateOf<List<Language>>(emptyList()) }
|
||||
@@ -160,7 +159,6 @@ fun AddCategoryDialog(
|
||||
)
|
||||
} else { // Dictionary
|
||||
DictionarySelectionContent(
|
||||
categoryViewModel = categoryViewModel,
|
||||
selectedPair = selectedDictionaryPair,
|
||||
onPairSelected = { selectedDictionaryPair = it }
|
||||
)
|
||||
@@ -251,11 +249,11 @@ fun AddCategoryDialog(
|
||||
|
||||
@Composable
|
||||
private fun DictionarySelectionContent(
|
||||
categoryViewModel: CategoryViewModel,
|
||||
selectedPair: Pair<Int, Int>?,
|
||||
onPairSelected: (Pair<Int, Int>) -> Unit
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
var allDictionaries by remember { mutableStateOf<List<Pair<Language, Language>>>(emptyList()) }
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
@@ -334,8 +332,7 @@ private fun DictionarySelectionContent(
|
||||
@Composable
|
||||
fun AddCategoryDialogPreview() {
|
||||
AddCategoryDialog(
|
||||
onDismiss = {},
|
||||
categoryViewModel = viewModel()
|
||||
onDismiss = {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -344,7 +341,6 @@ fun AddCategoryDialogPreview() {
|
||||
fun DictionarySelectionContentPreview() {
|
||||
LocalContext.current
|
||||
DictionarySelectionContent(
|
||||
categoryViewModel = viewModel(),
|
||||
selectedPair = Pair(R.string.language_1, R.string.language_2),
|
||||
onPairSelected = {}
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.dialogs
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
@@ -39,7 +38,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.VocabularyItem
|
||||
import eu.gaudian.translator.ui.theme.ThemePreviews
|
||||
@@ -51,32 +49,29 @@ import eu.gaudian.translator.view.composable.AppDialog
|
||||
import eu.gaudian.translator.view.composable.AppTextField
|
||||
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
|
||||
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.StatusViewModel
|
||||
import eu.gaudian.translator.viewmodel.TranslationViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
|
||||
enum class VocabularyDialogTab { SINGLE, MULTIPLE, TEXT }
|
||||
|
||||
@Composable
|
||||
fun AddVocabularyDialog(
|
||||
statusViewModel: StatusViewModel,
|
||||
languageViewModel: LanguageViewModel,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
initialWord: String? = null,
|
||||
translation: String? = null,
|
||||
onDismissRequest: () -> Unit,
|
||||
showMultiple: Boolean = true
|
||||
) {
|
||||
LocalContext.current
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val statusViewModel: StatusViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel = hiltViewModel<VocabularyViewModel>(viewModelStoreOwner = activity)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val translationViewModel: TranslationViewModel = viewModel()
|
||||
val categoryViewModel: CategoryViewModel = viewModel()
|
||||
val translationViewModel: TranslationViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val connectionConfigured = LocalConnectionConfigured.current
|
||||
|
||||
|
||||
@@ -235,6 +230,7 @@ fun AddVocabularyDialog(
|
||||
val isGenerating by vocabularyViewModel.isGenerating.collectAsState()
|
||||
val generated by vocabularyViewModel.generatedVocabularyItems.collectAsState()
|
||||
LaunchedEffect(isGenerating, generated) {
|
||||
@Suppress("ControlFlowWithEmptyBody")
|
||||
if (!isGenerating && showReview) {
|
||||
//TODO think if we still need this
|
||||
}
|
||||
@@ -341,14 +337,12 @@ fun AddVocabularyDialog(
|
||||
) {
|
||||
androidx.compose.material3.Surface(modifier = Modifier.fillMaxSize()) {
|
||||
VocabularyReviewScreen(
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onConfirm = { selectedItems, categoryIds ->
|
||||
vocabularyViewModel.addVocabularyItems(selectedItems, categoryIds)
|
||||
showReview = false
|
||||
onDismissRequest()
|
||||
},
|
||||
onCancel = { showReview = false },
|
||||
categoryViewModel = categoryViewModel
|
||||
onCancel = { showReview = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -358,14 +352,7 @@ fun AddVocabularyDialog(
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
fun AddVocabularyDialogPreview() {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val statusViewModel: StatusViewModel = viewModel()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as Application)
|
||||
|
||||
AddVocabularyDialog(
|
||||
statusViewModel = statusViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onDismissRequest = {})
|
||||
}
|
||||
@@ -20,14 +20,16 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.TagCategory
|
||||
import eu.gaudian.translator.model.VocabularyCategory
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.composable.AppCheckbox
|
||||
import eu.gaudian.translator.view.composable.AppDropdownMenuItem
|
||||
@@ -39,7 +41,6 @@ import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
|
||||
@Composable
|
||||
fun CategoryDropdown(
|
||||
categoryViewModel: CategoryViewModel,
|
||||
initialCategoryId: Int? = null,
|
||||
onCategorySelected: (List<VocabularyCategory?>) -> Unit,
|
||||
noneSelectable: Boolean? = true,
|
||||
@@ -47,6 +48,8 @@ fun CategoryDropdown(
|
||||
onlyLists: Boolean = false,
|
||||
addCategory: Boolean = false
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val categories by categoryViewModel.categories.collectAsState(initial = emptyList())
|
||||
val selectableCategories = if (onlyLists) categories.filterIsInstance<TagCategory>() else categories
|
||||
@@ -229,7 +232,6 @@ fun CategoryDropdown(
|
||||
@Composable
|
||||
fun CategoryDropdownPreview() {
|
||||
CategoryDropdown(
|
||||
categoryViewModel = viewModel(),
|
||||
onCategorySelected = {}
|
||||
)
|
||||
}
|
||||
@@ -18,11 +18,9 @@ import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.VocabularyCategory
|
||||
import eu.gaudian.translator.view.composable.AppDialog
|
||||
import eu.gaudian.translator.view.composable.DialogButton
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
|
||||
@Composable
|
||||
fun CategorySelectionDialog(
|
||||
viewModel: CategoryViewModel,
|
||||
onCategorySelected: (List<VocabularyCategory?>) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
@@ -35,7 +33,6 @@ fun CategorySelectionDialog(
|
||||
|
||||
|
||||
CategoryDropdown(
|
||||
categoryViewModel = viewModel,
|
||||
onCategorySelected = { categories ->
|
||||
selectedCategory = categories
|
||||
},
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
package eu.gaudian.translator.view.dialogs
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.utils.Log
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppAlertDialog
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
@Composable
|
||||
fun DeleteItemsDialog(
|
||||
viewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
onDismiss: () -> Unit,
|
||||
categoryId: Int,
|
||||
) {
|
||||
val categoryVocabularyItemDelete = viewModel.categoryVocabularyItemDelete
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val categoryVocabularyItemDelete = vocabularyViewModel.categoryVocabularyItemDelete
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
Log.d("DeleteItemsDialog", "categoryVocabularyItemDelete: $categoryVocabularyItemDelete")
|
||||
|
||||
@@ -32,7 +35,7 @@ fun DeleteItemsDialog(
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.deleteVocabularyItemsByCategory(categoryId)
|
||||
vocabularyViewModel.deleteVocabularyItemsByCategory(categoryId)
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.dialogs
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -25,15 +24,19 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppDialog
|
||||
import eu.gaudian.translator.view.composable.AppSlider
|
||||
import eu.gaudian.translator.view.composable.AppTextField
|
||||
@@ -41,17 +44,14 @@ import eu.gaudian.translator.view.composable.DialogButton
|
||||
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
|
||||
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
|
||||
import eu.gaudian.translator.view.hints.getImportVocabularyHint
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
@Composable
|
||||
fun ImportVocabularyDialog(
|
||||
onDismiss: () -> Unit,
|
||||
languageViewModel: LanguageViewModel,
|
||||
categoryViewModel: CategoryViewModel,
|
||||
vocabularyViewModel : VocabularyViewModel,
|
||||
optionalDescription: String? = null,
|
||||
optionalSearchTerm: String? = null
|
||||
@@ -64,7 +64,6 @@ fun ImportVocabularyDialog(
|
||||
navController = navController,
|
||||
onDismiss = onDismiss,
|
||||
languageViewModel = languageViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
optionalDescription = optionalDescription,
|
||||
optionalSearchTerm = optionalSearchTerm
|
||||
)
|
||||
@@ -78,13 +77,11 @@ fun ImportVocabularyDialog(
|
||||
// Full-screen surface to ensure the dialog covers content and stays above the main FAB/menu
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
VocabularyReviewScreen(
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onConfirm = { selectedItems, categoryIds ->
|
||||
vocabularyViewModel.addVocabularyItems(selectedItems, categoryIds)
|
||||
onDismiss()
|
||||
},
|
||||
onCancel = onDismiss,
|
||||
categoryViewModel = categoryViewModel
|
||||
onCancel = onDismiss
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -97,10 +94,11 @@ fun ImportDialogContent(
|
||||
navController: NavController,
|
||||
onDismiss: () -> Unit,
|
||||
languageViewModel: LanguageViewModel,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
optionalDescription: String? = null,
|
||||
optionalSearchTerm: String? = null
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
var category by remember { mutableStateOf(optionalSearchTerm ?: "") }
|
||||
var amount by remember { mutableFloatStateOf(1f) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@@ -205,4 +203,20 @@ fun ImportDialogContent(
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@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"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.dialogs
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -28,16 +27,12 @@ import eu.gaudian.translator.model.VocabularyStage
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppDialog
|
||||
import eu.gaudian.translator.view.composable.MultipleLanguageDropdown
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
@Composable
|
||||
fun StartExerciseDialog(
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
categoryViewModel: CategoryViewModel = CategoryViewModel.getInstance(applicationContext as Application),
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (
|
||||
categories: List<VocabularyCategory>,
|
||||
@@ -47,6 +42,7 @@ fun StartExerciseDialog(
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel : VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var lids by remember { mutableStateOf<List<Int>>(emptyList()) }
|
||||
var languages by remember { mutableStateOf<List<Language>>(emptyList()) }
|
||||
@@ -85,7 +81,6 @@ fun StartExerciseDialog(
|
||||
languages
|
||||
)
|
||||
CategoryDropdown(
|
||||
categoryViewModel = categoryViewModel,
|
||||
onCategorySelected = { categories ->
|
||||
selectedCategories = categories.filterIsInstance<VocabularyCategory>()
|
||||
},
|
||||
|
||||
@@ -11,26 +11,21 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppFabMenu
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.FabMenuItem
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.StatusViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
|
||||
@Composable
|
||||
fun VocabularyMenu(
|
||||
modifier: Modifier = Modifier,
|
||||
statusViewModel: StatusViewModel = viewModel(),
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application),
|
||||
categoryViewModel: CategoryViewModel = viewModel(),
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
var showAddVocabularyDialog by remember { mutableStateOf(false) }
|
||||
var showImportVocabularyDialog by remember { mutableStateOf(false) }
|
||||
var showAddCategoryDialog by remember { mutableStateOf(false) }
|
||||
@@ -57,9 +52,6 @@ fun VocabularyMenu(
|
||||
|
||||
if (showAddVocabularyDialog) {
|
||||
AddVocabularyDialog(
|
||||
statusViewModel = statusViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onDismissRequest = { showAddVocabularyDialog = false }
|
||||
)
|
||||
}
|
||||
@@ -67,7 +59,6 @@ fun VocabularyMenu(
|
||||
if (showImportVocabularyDialog) {
|
||||
ImportVocabularyDialog(
|
||||
languageViewModel = languageViewModel,
|
||||
categoryViewModel = categoryViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onDismiss = { showImportVocabularyDialog = false }
|
||||
)
|
||||
@@ -75,7 +66,6 @@ fun VocabularyMenu(
|
||||
|
||||
if (showAddCategoryDialog) {
|
||||
AddCategoryDialog(
|
||||
categoryViewModel = categoryViewModel,
|
||||
onDismiss = { showAddCategoryDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.dialogs
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -26,25 +25,25 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.VocabularyCategory
|
||||
import eu.gaudian.translator.model.VocabularyItem
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.composable.AppCheckbox
|
||||
import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
import eu.gaudian.translator.view.hints.getVocabularyReviewHint
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
@Composable
|
||||
fun VocabularyReviewScreen(
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
categoryViewModel: CategoryViewModel,
|
||||
onConfirm: (List<VocabularyItem>, List<Int>) -> Unit,
|
||||
onCancel: () -> Unit
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel : VocabularyViewModel = hiltViewModel(activity)
|
||||
val generatedItems: List<VocabularyItem> by vocabularyViewModel.generatedVocabularyItems.collectAsState()
|
||||
val selectedItems = remember { mutableStateListOf<VocabularyItem>() }
|
||||
val duplicates = remember { mutableStateListOf<Boolean>() }
|
||||
@@ -129,7 +128,6 @@ fun VocabularyReviewScreen(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
CategoryDropdown(
|
||||
categoryViewModel = categoryViewModel,
|
||||
onCategorySelected = { categories: List<VocabularyCategory?> ->
|
||||
selectedCategoryId = categories.filterNotNull().map { it.id }
|
||||
},
|
||||
|
||||
@@ -31,7 +31,6 @@ import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.DictionaryEntry
|
||||
@@ -50,8 +49,6 @@ import eu.gaudian.translator.view.dialogs.AddVocabularyDialog
|
||||
import eu.gaudian.translator.viewmodel.DictionaryViewModel
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||
import eu.gaudian.translator.viewmodel.StatusViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class DictionaryResultTab(
|
||||
@@ -76,8 +73,7 @@ fun DictionaryResultScreen(
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel: VocabularyViewModel = viewModel(viewModelStoreOwner = activity)
|
||||
val statusViewModel: StatusViewModel = viewModel()
|
||||
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
// State Collection
|
||||
@@ -214,9 +210,6 @@ fun DictionaryResultScreen(
|
||||
if (showAddVocabularyDialog) {
|
||||
entryData?.let {
|
||||
AddVocabularyDialog(
|
||||
statusViewModel = statusViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
initialWord = it.entry.word,
|
||||
onDismissRequest = {
|
||||
@Suppress("AssignedValueIsNeverRead")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package eu.gaudian.translator.view.dictionary
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
@@ -14,10 +14,13 @@ import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
fun DictionaryScreen(
|
||||
navController: NavController,
|
||||
onEntryClick: (eu.gaudian.translator.model.DictionaryEntry) -> Unit = {},
|
||||
dictionaryViewModel: DictionaryViewModel,
|
||||
languageViewModel: LanguageViewModel,
|
||||
onNavigateToOptions: () -> Unit
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
|
||||
// Use the new refactored component
|
||||
DictionaryScreenContent(
|
||||
navController = navController,
|
||||
@@ -31,15 +34,10 @@ fun DictionaryScreen(
|
||||
@Preview
|
||||
@Composable
|
||||
fun DictionaryScreenPreview() {
|
||||
val activity = androidx.compose.ui.platform.LocalContext.current.findActivity()
|
||||
val dictionaryViewModel: DictionaryViewModel = viewModel()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
DictionaryScreen(
|
||||
navController = rememberNavController(),
|
||||
onEntryClick = {},
|
||||
dictionaryViewModel = dictionaryViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
onNavigateToOptions = {}
|
||||
)
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import eu.gaudian.translator.R
|
||||
@@ -98,7 +97,7 @@ fun EtymologyScreen(
|
||||
@Composable
|
||||
fun EtymologyScreenPreview() {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val dictionaryViewModel: DictionaryViewModel = viewModel()
|
||||
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
EtymologyScreen(
|
||||
navController = rememberNavController(),
|
||||
|
||||
@@ -58,7 +58,7 @@ import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.Language
|
||||
import eu.gaudian.translator.model.grammar.DictionaryEntryData
|
||||
@@ -71,6 +71,7 @@ import eu.gaudian.translator.model.grammar.UnifiedMorphology
|
||||
import eu.gaudian.translator.model.grammar.UnifiedMorphologyParser
|
||||
import eu.gaudian.translator.utils.dictionary.LocalDictionaryWordInfo
|
||||
import eu.gaudian.translator.utils.dictionary.PartOfSpeechTranslator
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AutoResizeSingleLineText
|
||||
import eu.gaudian.translator.viewmodel.DictionaryViewModel
|
||||
@@ -92,7 +93,8 @@ fun RichLocalEntryDisplay(
|
||||
expandable: Boolean = true, // Enables whole-entry collapsing
|
||||
initiallyExpanded: Boolean = true // Default state
|
||||
) {
|
||||
val languageConfigViewModel: LanguageConfigViewModel = viewModel()
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(activity)
|
||||
val allConfigs by languageConfigViewModel.configs.collectAsState()
|
||||
val languageConfig = allConfigs[langCode]
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package eu.gaudian.translator.view.dictionary
|
||||
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -11,11 +10,8 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import eu.gaudian.translator.R
|
||||
@@ -46,17 +42,12 @@ private data class DictionaryTab(override val title: String, override val icon:
|
||||
fun MainDictionaryScreen(
|
||||
navController: NavController
|
||||
) {
|
||||
val viewModelStoreOwner = if (LocalInspectionMode.current) {
|
||||
null
|
||||
} else {
|
||||
LocalActivity.current
|
||||
}
|
||||
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
|
||||
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val correctionViewModel: CorrectionViewModel = viewModel(viewModelStoreOwner = viewModelStoreOwner as ViewModelStoreOwner)
|
||||
val correctionViewModel: CorrectionViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val dictionaryTabs = getDictionaryTabs()
|
||||
|
||||
@@ -78,8 +69,6 @@ fun MainDictionaryScreen(
|
||||
when (selectedTab) {
|
||||
dictionaryTabs[0] -> DictionaryScreen(
|
||||
navController = navController,
|
||||
dictionaryViewModel = dictionaryViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
onEntryClick = { entry ->
|
||||
// Set flag indicating navigation is from external source (not DictionaryResultScreen)
|
||||
dictionaryViewModel.setNavigatingFromDictionaryResult(false)
|
||||
|
||||
@@ -22,11 +22,13 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.Exercise
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.composable.AppCard
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
@@ -40,7 +42,8 @@ fun ExerciseListScreen(
|
||||
onLongExerciseClicked: (Exercise) -> Unit,
|
||||
onDeleteClicked: (Exercise) -> Unit,
|
||||
) {
|
||||
val exerciseViewModel: ExerciseViewModel = viewModel()
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val exerciseViewModel: ExerciseViewModel = hiltViewModel(activity)
|
||||
val exercises by exerciseViewModel.exercises.collectAsState()
|
||||
|
||||
AppOutlinedCard{
|
||||
|
||||
@@ -33,11 +33,12 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import eu.gaudian.translator.R
|
||||
@@ -52,6 +53,7 @@ import eu.gaudian.translator.model.VocabularyTestQuestion
|
||||
import eu.gaudian.translator.model.WordOrderQuestion
|
||||
import eu.gaudian.translator.ui.theme.ThemePreviews
|
||||
import eu.gaudian.translator.ui.theme.semanticColors
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.ComponentDefaults
|
||||
@@ -65,9 +67,11 @@ import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
@Composable
|
||||
fun ExerciseSessionScreen(
|
||||
navController: NavController,
|
||||
exerciseViewModel: ExerciseViewModel, // Changed: No longer creates its own instance
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application)
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val exerciseViewModel: ExerciseViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
val sessionState by exerciseViewModel.exerciseSessionState.collectAsState()
|
||||
|
||||
var showVocabulary by remember { mutableStateOf(true) }
|
||||
@@ -267,7 +271,9 @@ fun ExerciseQuestionScreen(
|
||||
onCloseClick: () -> Unit,
|
||||
navController: NavController
|
||||
) {
|
||||
val exerciseViewModel = viewModel<ExerciseViewModel>()
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val exerciseViewModel = hiltViewModel<ExerciseViewModel>(viewModelStoreOwner = activity)
|
||||
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
||||
@@ -24,11 +24,14 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.Exercise
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppAlertDialog
|
||||
import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
@@ -52,13 +55,13 @@ enum class ExerciseTab(override val title: String, override val icon: ImageVecto
|
||||
* showing a tabbed layout for the dashboard and the list of all exercises.
|
||||
*
|
||||
* @param navController The main NavController to handle navigation to an exercise session.
|
||||
* @param exerciseViewModel The ViewModel for managing exercise data and state.
|
||||
*/
|
||||
@Composable
|
||||
fun MainExerciseScreen(
|
||||
navController: NavController,
|
||||
exerciseViewModel: ExerciseViewModel // Changed: No longer creates its own instance
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val exerciseViewModel : ExerciseViewModel = hiltViewModel(activity)
|
||||
val aiState by exerciseViewModel.aiGenerationState.collectAsState()
|
||||
var exerciseToDelete by remember { mutableStateOf<Exercise?>(null) }
|
||||
var showGenerateDialog by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -29,8 +29,10 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
@@ -45,9 +47,11 @@ import java.net.URLDecoder
|
||||
@Composable
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
fun YouTubeBrowserScreen(
|
||||
navController: NavController,
|
||||
exerciseViewModel: ExerciseViewModel
|
||||
navController: NavController
|
||||
) {
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val exerciseViewModel : ExerciseViewModel = hiltViewModel(activity)
|
||||
var pendingVideoUrl by remember { mutableStateOf<String?>(null) }
|
||||
var showLanguageDialog by remember { mutableStateOf(false) }
|
||||
var showCustomDialog by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -37,6 +37,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer
|
||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
|
||||
@@ -44,6 +45,7 @@ import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.options.IFram
|
||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.utils.loadOrCueVideo
|
||||
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
@@ -53,9 +55,10 @@ import eu.gaudian.translator.viewmodel.YouTubeExerciseState
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
fun YouTubeExerciseScreen(
|
||||
navController: NavController,
|
||||
exerciseViewModel: ExerciseViewModel
|
||||
navController: NavController
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val exerciseViewModel : ExerciseViewModel = hiltViewModel(activity)
|
||||
val context = LocalContext.current
|
||||
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
|
||||
val sessionParams by exerciseViewModel.youTubeSessionParams.collectAsState()
|
||||
|
||||
@@ -40,7 +40,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.BuildConfig
|
||||
import eu.gaudian.translator.R
|
||||
@@ -370,8 +369,9 @@ private fun DeveloperOptions(
|
||||
navController: NavController,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val statusViewModel: StatusViewModel = viewModel()
|
||||
|
||||
val activity = context.findActivity()
|
||||
val statusViewModel: StatusViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
|
||||
|
||||
@@ -59,10 +59,11 @@ import kotlin.math.roundToInt
|
||||
@Composable
|
||||
fun VocabularyProgressOptionsScreen(
|
||||
navController: NavController,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
|
||||
|
||||
val exportFileLauncher = rememberLauncherForActivityResult(
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package eu.gaudian.translator.view.settings
|
||||
|
||||
import android.app.Application
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -53,15 +52,17 @@ import eu.gaudian.translator.view.composable.SingleLanguageDropDown
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.StatusViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
@Composable
|
||||
fun VocabularyRepositoryOptionsScreen(
|
||||
navController: NavController,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
statusViewModel: StatusViewModel = StatusViewModel.getInstance(applicationContext as Application)
|
||||
navController: NavController
|
||||
) {
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val statusViewModel: StatusViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
|
||||
val context = LocalContext.current
|
||||
val repositoryStateImportedFrom = stringResource(R.string.repository_state_imported_from)
|
||||
|
||||
@@ -79,7 +80,6 @@ fun VocabularyRepositoryOptionsScreen(
|
||||
)
|
||||
|
||||
// CSV/Excel import state
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val showTableImportDialog = remember { mutableStateOf(false) }
|
||||
var parsedTable by remember { mutableStateOf<List<List<String>>>(emptyList()) }
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package eu.gaudian.translator.view.translation
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
@@ -64,16 +63,13 @@ import eu.gaudian.translator.view.hints.TranslationScreenHint
|
||||
import eu.gaudian.translator.view.settings.SettingsRoutes
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||
import eu.gaudian.translator.viewmodel.StatusViewModel
|
||||
import eu.gaudian.translator.viewmodel.TranslationViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun TranslationScreen(
|
||||
translationViewModel: TranslationViewModel,
|
||||
languageViewModel: LanguageViewModel,
|
||||
statusViewModel: StatusViewModel,
|
||||
onHistoryClick: () -> Unit,
|
||||
onSettingsClick: () -> Unit,
|
||||
navController: NavHostController
|
||||
@@ -82,10 +78,6 @@ fun TranslationScreen(
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
val vocabularyViewModel = remember(context) {
|
||||
VocabularyViewModel.getInstance(context.applicationContext as Application)
|
||||
}
|
||||
|
||||
|
||||
val isInitializationComplete by settingsViewModel.isInitialized.collectAsStateWithLifecycle(
|
||||
initialValue = true,
|
||||
@@ -111,9 +103,7 @@ fun TranslationScreen(
|
||||
LoadedTranslationContent(
|
||||
translationViewModel = translationViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
statusViewModel = statusViewModel,
|
||||
settingsViewModel = settingsViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onHistoryClick = onHistoryClick,
|
||||
onSettingsClick = onSettingsClick,
|
||||
context = context
|
||||
@@ -126,9 +116,7 @@ fun TranslationScreen(
|
||||
private fun LoadedTranslationContent(
|
||||
translationViewModel: TranslationViewModel,
|
||||
languageViewModel: LanguageViewModel,
|
||||
statusViewModel: StatusViewModel,
|
||||
settingsViewModel: SettingsViewModel,
|
||||
vocabularyViewModel: VocabularyViewModel,
|
||||
onHistoryClick: () -> Unit,
|
||||
onSettingsClick: () -> Unit,
|
||||
context: Context
|
||||
@@ -163,9 +151,6 @@ private fun LoadedTranslationContent(
|
||||
|
||||
if (showAddVocabularyDialog) {
|
||||
AddVocabularyDialog(
|
||||
statusViewModel = statusViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
initialWord = inputText,
|
||||
translation = translatedText,
|
||||
onDismissRequest = { showAddVocabularyDialog = false },
|
||||
|
||||
@@ -11,14 +11,12 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import eu.gaudian.translator.utils.TextToSpeechHelper
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||
import eu.gaudian.translator.viewmodel.StatusViewModel
|
||||
import eu.gaudian.translator.viewmodel.TranslationViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -26,11 +24,10 @@ import kotlinx.coroutines.launch
|
||||
@Composable
|
||||
fun TranslationScreen(
|
||||
navController: NavController,
|
||||
statusViewModel: StatusViewModel = viewModel(),
|
||||
translationViewModel: TranslationViewModel = viewModel(),
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val translationViewModel: TranslationViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
var showHistorySheet by remember { mutableStateOf(false) }
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
|
||||
@@ -42,7 +39,6 @@ fun TranslationScreen(
|
||||
TranslationScreen(
|
||||
translationViewModel = translationViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
statusViewModel = statusViewModel,
|
||||
onHistoryClick = { showHistorySheet = true },
|
||||
onSettingsClick = {
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
@@ -92,7 +88,5 @@ fun TranslationScreenPreview() {
|
||||
val navController = NavController(LocalContext.current)
|
||||
TranslationScreen(
|
||||
navController = navController,
|
||||
statusViewModel = viewModel(),
|
||||
translationViewModel = viewModel(),
|
||||
)
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.TagCategory
|
||||
@@ -63,10 +62,10 @@ fun CategoryDetailScreen(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val categoryViewModel: CategoryViewModel = viewModel(viewModelStoreOwner = activity)
|
||||
val progressViewModel: ProgressViewModel = ProgressViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
|
||||
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(activity.application as android.app.Application)
|
||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val progressViewModel: ProgressViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
val category by categoryViewModel.getCategoryById(categoryId).collectAsState(initial = null)
|
||||
val categoryProgressList by progressViewModel.categoryProgressList.collectAsState()
|
||||
@@ -254,7 +253,6 @@ fun CategoryDetailScreen(
|
||||
if (showDeleteItemsDialog) {
|
||||
DeleteItemsDialog(
|
||||
onDismiss = { categoryViewModel.setShowDeleteItemsDialog(false, categoryId) },
|
||||
viewModel = vocabularyViewModel,
|
||||
categoryId = categoryId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,11 +31,14 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.TagCategory
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
@@ -57,12 +60,16 @@ enum class SortOption {
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun CategoryListScreen(
|
||||
categoryViewModel: CategoryViewModel,
|
||||
progressViewModel: ProgressViewModel,
|
||||
onNavigateBack: () -> Unit,
|
||||
onCategoryClicked: (Int) -> Unit,
|
||||
) {
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val categoryViewModel : CategoryViewModel = hiltViewModel(activity)
|
||||
val progressViewModel : ProgressViewModel = hiltViewModel(activity)
|
||||
|
||||
|
||||
|
||||
var sortOption by remember { mutableStateOf(SortOption.NAME) }
|
||||
var sortMenuExpanded by remember { mutableStateOf(false) }
|
||||
val categoryProgressList by progressViewModel.categoryProgressList.collectAsState(initial = emptyList())
|
||||
@@ -75,7 +82,6 @@ fun CategoryListScreen(
|
||||
if (showAddCategoryDialog) {
|
||||
AddCategoryDialog(
|
||||
onDismiss = { showAddCategoryDialog = false },
|
||||
categoryViewModel = categoryViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package eu.gaudian.translator.view.vocabulary
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
@@ -89,14 +88,15 @@ fun DashboardContent(
|
||||
onNavigateToCategoryDetail: (Int) -> Unit,
|
||||
onNavigateToCategoryList: () -> Unit,
|
||||
onShowWordPairExerciseDialog: () -> Unit,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as Application),
|
||||
progressViewModel: ProgressViewModel = ProgressViewModel.getInstance(LocalContext.current.applicationContext as Application),
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val progressViewModel: ProgressViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
var showMissingLanguageDialog by remember { mutableStateOf(false) }
|
||||
var selectedMissingLanguageId by remember { mutableStateOf<Int?>(null) }
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
val affectedItems by remember(selectedMissingLanguageId) {
|
||||
selectedMissingLanguageId?.let {
|
||||
@@ -630,7 +630,6 @@ private fun LazyStatusWidget(
|
||||
}
|
||||
} else {
|
||||
StatusWidget(
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onNavigateToNew = onNavigateToNew,
|
||||
onNavigateToDuplicates = onNavigateToDuplicates,
|
||||
onNavigateToFaulty = onNavigateToFaulty,
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -45,6 +46,7 @@ import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -52,17 +54,27 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.LanguageLevels
|
||||
import eu.gaudian.translator.model.MyAppLanguageLevel
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppDialog
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
import eu.gaudian.translator.viewmodel.ProgressViewModel
|
||||
|
||||
@Composable
|
||||
fun LanguageProgressScreen(wordsLearned: Int, navController: NavController) {
|
||||
fun LanguageProgressScreen(navController: NavController) {
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val progressViewModel : ProgressViewModel = hiltViewModel(activity)
|
||||
|
||||
val wordsLearned = progressViewModel.totalWordsCompleted.collectAsState().value
|
||||
|
||||
|
||||
AppScaffold(
|
||||
topBar = {
|
||||
AppTopAppBar(
|
||||
@@ -368,5 +380,5 @@ private fun LevelDetailDialog(level: MyAppLanguageLevel, onDismiss: () -> Unit)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun LanguageProgressScreenPreview() {
|
||||
LanguageProgressScreen(wordsLearned = 50000, navController = NavController(androidx.compose.ui.platform.LocalContext.current))
|
||||
LanguageProgressScreen(navController = NavController(LocalContext.current))
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package eu.gaudian.translator.view.vocabulary
|
||||
|
||||
import android.app.Application
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
@@ -34,7 +33,7 @@ import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
@@ -59,7 +58,6 @@ import eu.gaudian.translator.view.composable.OptionItemSwitch
|
||||
import eu.gaudian.translator.view.composable.TabItem
|
||||
import eu.gaudian.translator.view.dialogs.StartExerciseDialog
|
||||
import eu.gaudian.translator.view.dialogs.VocabularyMenu
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.ExerciseViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.flow.first
|
||||
@@ -94,9 +92,8 @@ fun MainVocabularyScreen(
|
||||
) {
|
||||
|
||||
val activity = LocalActivity.current as ComponentActivity
|
||||
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(activity.application as Application)
|
||||
val categoryViewModel: CategoryViewModel = viewModel(viewModelStoreOwner = activity)
|
||||
val exerciseViewModel: ExerciseViewModel = viewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val exerciseViewModel: ExerciseViewModel = hiltViewModel(activity)
|
||||
val vocabularyNavController = rememberNavController()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var showCustomExerciseDialog by remember { mutableStateOf(false) }
|
||||
@@ -117,8 +114,6 @@ fun MainVocabularyScreen(
|
||||
|
||||
if (showCustomExerciseDialog) {
|
||||
StartExerciseDialog(
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
categoryViewModel = categoryViewModel,
|
||||
onDismiss = { showCustomExerciseDialog = false },
|
||||
onConfirm = { categories, stages, languageIds ->
|
||||
showCustomExerciseDialog = false
|
||||
@@ -133,8 +128,6 @@ fun MainVocabularyScreen(
|
||||
|
||||
if (showWordPairExerciseDialog) {
|
||||
StartExerciseDialog(
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
categoryViewModel = categoryViewModel,
|
||||
onDismiss = { showWordPairExerciseDialog = false },
|
||||
onConfirm = { categories, stages, languageIds ->
|
||||
// Store selections and open settings dialog instead of starting immediately
|
||||
|
||||
@@ -30,11 +30,14 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AppSlider
|
||||
@@ -45,14 +48,16 @@ import kotlinx.coroutines.launch
|
||||
@Composable
|
||||
fun NoGrammarItemsScreen(
|
||||
navController: NavController,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application)
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
val itemsWithoutGrammar by vocabularyViewModel.allItemsWithoutGrammar.collectAsState()
|
||||
val isGenerating by vocabularyViewModel.isGenerating.collectAsState()
|
||||
|
||||
var showFetchGrammarDialog by remember { mutableStateOf(false) }
|
||||
|
||||
@Suppress("UnusedVariable") val onClose = { navController.popBackStack() }
|
||||
@Suppress("UnusedVariable", "unused", "HardCodedStringLiteral") val onClose = { navController.popBackStack() }
|
||||
|
||||
if (itemsWithoutGrammar.isEmpty() && !isGenerating) {
|
||||
Column(
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package eu.gaudian.translator.view.vocabulary
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -32,20 +31,16 @@ import eu.gaudian.translator.view.LocalConnectionConfigured
|
||||
import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.dialogs.AddVocabularyDialog
|
||||
import eu.gaudian.translator.view.dialogs.ImportVocabularyDialog
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.StatusViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
|
||||
@Composable
|
||||
fun NoVocabularyScreen(){
|
||||
|
||||
val application = LocalContext.current.applicationContext as Application
|
||||
val statusViewModel = StatusViewModel.getInstance(application)
|
||||
val activity = LocalContext.current.findActivity()
|
||||
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val vocabularyViewModel = VocabularyViewModel.getInstance(application)
|
||||
val categoryViewModel = CategoryViewModel.getInstance(application)
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
|
||||
var showAddVocabularyDialog by remember { mutableStateOf(false) }
|
||||
@@ -56,9 +51,6 @@ fun NoVocabularyScreen(){
|
||||
|
||||
if (showAddVocabularyDialog) {
|
||||
AddVocabularyDialog(
|
||||
statusViewModel = statusViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onDismissRequest = { showAddVocabularyDialog = false }
|
||||
)
|
||||
}
|
||||
@@ -66,7 +58,6 @@ fun NoVocabularyScreen(){
|
||||
if (showImportVocabularyDialog) {
|
||||
ImportVocabularyDialog(
|
||||
languageViewModel = languageViewModel,
|
||||
categoryViewModel = categoryViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onDismiss = { showImportVocabularyDialog = false }
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.vocabulary
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -10,24 +9,28 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.VocabularyStage
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
import eu.gaudian.translator.view.vocabulary.widgets.DetailedStageProgressBar
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
@Composable
|
||||
fun StageDetailScreen(
|
||||
navController: NavController,
|
||||
stage: VocabularyStage,
|
||||
viewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val viewModel = hiltViewModel<VocabularyViewModel>(viewModelStoreOwner = activity)
|
||||
|
||||
val stageMapping by viewModel.stageMapping.collectAsState()
|
||||
val dueTodayItems by viewModel.dueTodayItems.collectAsState()
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.vocabulary
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
@@ -79,9 +78,9 @@ fun StartScreen(
|
||||
selectedTargetLanguage: Language?,
|
||||
onTargetLanguageChanged: (Language?) -> Unit,
|
||||
) {
|
||||
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(
|
||||
LocalContext.current.applicationContext as Application
|
||||
)
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val dueTodayItems by vocabularyViewModel.dueTodayItems.collectAsState(initial = emptyList())
|
||||
val allItems = cardSet?.cards ?: emptyList()
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.vocabulary
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -28,7 +27,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.VocabularyItem
|
||||
@@ -44,7 +42,6 @@ import eu.gaudian.translator.view.dialogs.CategorySelectionDialog
|
||||
import eu.gaudian.translator.view.dialogs.ImportVocabularyDialog
|
||||
import eu.gaudian.translator.view.dialogs.StageSelectionDialog
|
||||
import eu.gaudian.translator.view.vocabulary.card.VocabularyCard
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -61,9 +58,8 @@ fun VocabularyCardHost(
|
||||
onBackPressed: (() -> Unit)? = null
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as Application)
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val categoryViewModel: CategoryViewModel = viewModel()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val navigationItems by vocabularyViewModel.currentNavigationItems.collectAsState()
|
||||
@@ -161,6 +157,7 @@ fun VocabularyCardHost(
|
||||
lifecycle = lifecycleOwner.lifecycle
|
||||
)
|
||||
|
||||
@Suppress("ControlFlowWithEmptyBody")
|
||||
if (exerciseMode && onBackPressed != null) {
|
||||
//TODO consider BackPressHandler(onBackPressed = { showQuitDialog = true })
|
||||
}
|
||||
@@ -213,7 +210,6 @@ fun VocabularyCardHost(
|
||||
onDeleteClick = { showDeleteDialog = true },
|
||||
navController = navController,
|
||||
isUserSpellingCorrect = false,
|
||||
vocabularyViewModel = vocabularyViewModel
|
||||
)
|
||||
|
||||
// Dialogs are unaffected by the layout change
|
||||
@@ -237,7 +233,6 @@ fun VocabularyCardHost(
|
||||
|
||||
if (showCategoryDialog) {
|
||||
CategorySelectionDialog(
|
||||
viewModel = categoryViewModel,
|
||||
onCategorySelected = {
|
||||
vocabularyViewModel.addVocabularyItemToCategories(
|
||||
listOf(currentVocabularyItem),
|
||||
@@ -270,12 +265,12 @@ fun VocabularyCardHost(
|
||||
languageViewModel = languageViewModel,
|
||||
optionalDescription = stringResource(R.string.generate_related_vocabulary_items),
|
||||
optionalSearchTerm = currentVocabularyItem.wordFirst,
|
||||
categoryViewModel = categoryViewModel,
|
||||
vocabularyViewModel = vocabularyViewModel
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(spellingMode) {
|
||||
@Suppress("ControlFlowWithEmptyBody")
|
||||
if (spellingMode) {
|
||||
//TODO implement // Setup spelling mode logic if needed
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.composable.ComponentDefaults
|
||||
import eu.gaudian.translator.view.vocabulary.card.VocabularyCard
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
|
||||
/**
|
||||
* Represents the different types of exercises available.
|
||||
@@ -140,15 +139,14 @@ sealed interface VocabularyExerciseAction {
|
||||
fun GuessingExercise(
|
||||
state: VocabularyExerciseState.Guessing,
|
||||
navController: NavController,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
|
||||
) {
|
||||
|
||||
VocabularyCard(
|
||||
vocabularyItem = state.item,
|
||||
isFlipped = state.isRevealed,
|
||||
navController = navController,
|
||||
exerciseMode = true,
|
||||
switchOrder = state.isSwitched,
|
||||
vocabularyViewModel = vocabularyViewModel
|
||||
)
|
||||
}
|
||||
|
||||
@@ -158,8 +156,8 @@ fun GuessingExercise(
|
||||
fun SpellingExercise(
|
||||
state: VocabularyExerciseState.Spelling,
|
||||
navController: NavController,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
|
||||
) {
|
||||
|
||||
VocabularyCard(
|
||||
vocabularyItem = state.item,
|
||||
isFlipped = state.isRevealed,
|
||||
@@ -168,7 +166,6 @@ fun SpellingExercise(
|
||||
navController = navController,
|
||||
exerciseMode = true,
|
||||
switchOrder = state.isSwitched,
|
||||
vocabularyViewModel = vocabularyViewModel
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.Language
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppAlertDialog
|
||||
import eu.gaudian.translator.viewmodel.ExerciseConfig
|
||||
import eu.gaudian.translator.viewmodel.ScreenState
|
||||
@@ -49,8 +51,9 @@ fun VocabularyExerciseHostScreen(
|
||||
onClose: () -> Unit,
|
||||
navController: NavController
|
||||
) {
|
||||
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application)
|
||||
val exerciseViewModel: VocabularyExerciseViewModel = viewModel()
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val exerciseViewModel: VocabularyExerciseViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
val cardSet by vocabularyViewModel.cardSet.collectAsState()
|
||||
val screenState by exerciseViewModel.screenState.collectAsState()
|
||||
|
||||
@@ -42,14 +42,17 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
@@ -75,8 +78,11 @@ private data class HeatmapMonth(val yearMonth: YearMonth, val weeks: List<List<L
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@Composable
|
||||
fun VocabularyHeatmapScreen(
|
||||
viewModel: ProgressViewModel = ProgressViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application), navController: NavController
|
||||
navController: NavController
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val viewModel : ProgressViewModel = hiltViewModel(activity)
|
||||
|
||||
// State and derived data
|
||||
val dailyStats by viewModel.dailyVocabularyStats.collectAsState()
|
||||
val today = remember { Clock.System.todayIn(TimeZone.currentSystemDefault()) }
|
||||
|
||||
@@ -71,7 +71,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import eu.gaudian.translator.R
|
||||
@@ -126,9 +125,9 @@ fun VocabularyListScreen(
|
||||
val scope = rememberCoroutineScope()
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val lazyListState = rememberLazyListState()
|
||||
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(LocalContext.current.applicationContext as android.app.Application)
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val categoryViewModel: CategoryViewModel = viewModel()
|
||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val context = LocalContext.current
|
||||
|
||||
var filterState by rememberSaveable {
|
||||
@@ -380,7 +379,6 @@ fun VocabularyListScreen(
|
||||
showFilterSheet = false
|
||||
scope.launch { lazyListState.scrollToItem(0) }
|
||||
},
|
||||
categoryViewModel = categoryViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
languagesPresent = allLanguages.filter { it.nameResId in languagesPresent },
|
||||
hideCategory = categoryId != null && categoryId != 0,
|
||||
@@ -391,7 +389,6 @@ fun VocabularyListScreen(
|
||||
if (showCategoryDialog) {
|
||||
val selectedItems = vocabularyItems.filter { selection.contains(it.id.toLong()) }
|
||||
CategorySelectionDialog(
|
||||
viewModel = categoryViewModel,
|
||||
onCategorySelected = {
|
||||
vocabularyViewModel.addVocabularyItemToCategories(
|
||||
selectedItems,
|
||||
@@ -805,7 +802,6 @@ fun LanguageRowPreview() {
|
||||
@Composable
|
||||
private fun FilterSortBottomSheet(
|
||||
currentFilterState: VocabularyFilterState,
|
||||
categoryViewModel: CategoryViewModel,
|
||||
languageViewModel: LanguageViewModel,
|
||||
languagesPresent: List<Language>,
|
||||
onDismiss: () -> Unit,
|
||||
@@ -821,8 +817,9 @@ private fun FilterSortBottomSheet(
|
||||
|
||||
val context = LocalContext.current
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
val activity = LocalContext.current.findActivity()
|
||||
|
||||
val languageConfigViewModel: LanguageConfigViewModel = viewModel()
|
||||
val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(activity)
|
||||
val allWordClasses by languageConfigViewModel.allWordClasses.collectAsStateWithLifecycle()
|
||||
|
||||
ModalBottomSheet(
|
||||
@@ -896,7 +893,6 @@ private fun FilterSortBottomSheet(
|
||||
Text(stringResource(R.string.label_category), style = MaterialTheme.typography.titleMedium)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
CategoryDropdown(
|
||||
categoryViewModel = categoryViewModel,
|
||||
initialCategoryId = selectedCategoryId,
|
||||
onCategorySelected = { categories ->
|
||||
selectedCategoryId = categories.firstOrNull()?.id
|
||||
@@ -958,11 +954,9 @@ private fun FilterSortBottomSheet(
|
||||
@Composable
|
||||
fun FilterSortBottomSheetPreview() {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val categoryViewModel: CategoryViewModel = viewModel()
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
FilterSortBottomSheet(
|
||||
currentFilterState = VocabularyFilterState(),
|
||||
categoryViewModel = categoryViewModel,
|
||||
languageViewModel = languageViewModel,
|
||||
languagesPresent = emptyList(),
|
||||
onDismiss = {},
|
||||
|
||||
@@ -56,7 +56,6 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.Language
|
||||
@@ -82,7 +81,6 @@ import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
enum class FilterMode {
|
||||
NEW, DUPLICATES, FAULTY
|
||||
@@ -90,11 +88,14 @@ enum class FilterMode {
|
||||
|
||||
@Composable
|
||||
fun VocabularySortingScreen(
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as android.app.Application),
|
||||
categoryViewModel: CategoryViewModel = CategoryViewModel.getInstance(applicationContext as android.app.Application),
|
||||
navController: NavHostController,
|
||||
initialFilterMode: String? = null
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
|
||||
var sortOrder by remember { mutableStateOf(VocabularyViewModel.SortOrder.NEWEST_FIRST) }
|
||||
val allDuplicateItems by vocabularyViewModel.allDuplicateItems.collectAsState()
|
||||
val allFaultyItems by vocabularyViewModel.allFaultyItems.collectAsState()
|
||||
@@ -263,9 +264,7 @@ fun VocabularySortingScreen(
|
||||
VocabularySortingItem(
|
||||
item = item,
|
||||
filterMode = filterMode, // NEW: Pass the mode to the item
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
categoryViewModel = categoryViewModel,
|
||||
languageConfigViewModel = viewModel()
|
||||
vocabularyViewModel = vocabularyViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -286,8 +285,6 @@ fun VocabularySortingScreen(
|
||||
@Composable
|
||||
fun VocabularySortingScreenPreview() {
|
||||
VocabularySortingScreen(
|
||||
vocabularyViewModel = viewModel(),
|
||||
categoryViewModel = viewModel(),
|
||||
navController = NavHostController(LocalContext.current),
|
||||
initialFilterMode = "NEW"
|
||||
)
|
||||
@@ -296,12 +293,11 @@ fun VocabularySortingScreenPreview() {
|
||||
@Composable
|
||||
fun VocabularySortingItem(
|
||||
item: VocabularyItem,
|
||||
filterMode: FilterMode?, // NEW: Receive the current filter mode
|
||||
vocabularyViewModel: VocabularyViewModel,
|
||||
categoryViewModel: CategoryViewModel,
|
||||
languageConfigViewModel: LanguageConfigViewModel = viewModel()
|
||||
filterMode: FilterMode?,
|
||||
vocabularyViewModel: VocabularyViewModel
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
var wordFirst by remember { mutableStateOf(item.wordFirst) }
|
||||
var wordSecond by remember { mutableStateOf(item.wordSecond) }
|
||||
@@ -487,7 +483,6 @@ fun VocabularySortingItem(
|
||||
}
|
||||
|
||||
CategoryDropdown(
|
||||
categoryViewModel = categoryViewModel,
|
||||
onCategorySelected = { categories ->
|
||||
selectedCategories = categories.mapNotNull { it?.id }
|
||||
},
|
||||
@@ -560,9 +555,7 @@ fun VocabularySortingItemPreview() {
|
||||
VocabularySortingItem(
|
||||
item = item,
|
||||
filterMode = FilterMode.NEW,
|
||||
vocabularyViewModel = viewModel(),
|
||||
categoryViewModel = viewModel(),
|
||||
languageConfigViewModel = viewModel()
|
||||
vocabularyViewModel = hiltViewModel()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ fun AdditionalContentBottomSheet(
|
||||
) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
|
||||
val vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application)
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@@ -225,7 +225,6 @@ fun AdditionalContentBottomSheet(
|
||||
SynonymsDisplay(
|
||||
synonyms = synonyms,
|
||||
loading = synonymsLoading,
|
||||
vocabularyViewModel = vocabularyViewModel,
|
||||
onSynonymAdded = handleSynonymAdded,
|
||||
onReload = refreshSynonyms
|
||||
)
|
||||
@@ -320,10 +319,12 @@ private fun ExampleSentencesDisplayLoadedPreview() {
|
||||
private fun SynonymsDisplay(
|
||||
synonyms: List<SynonymDisplayState>?,
|
||||
loading: Boolean,
|
||||
vocabularyViewModel: VocabularyViewModel,
|
||||
onSynonymAdded: () -> Unit,
|
||||
onReload: () -> Unit,
|
||||
) {
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -387,7 +388,7 @@ private fun SynonymsDisplay(
|
||||
@Composable
|
||||
private fun SynonymsDisplayLoadingPreview() {
|
||||
// This preview doesn't have a real VocabularyViewModel, so actions will not work.
|
||||
SynonymsDisplay(synonyms = null, loading = false, vocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application), onSynonymAdded = {}, onReload = {})
|
||||
SynonymsDisplay(synonyms = null, loading = false, onSynonymAdded = {}, onReload = {})
|
||||
}
|
||||
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
@@ -415,7 +416,7 @@ private fun SynonymsDisplayLoadedPreview() {
|
||||
proximity = null
|
||||
)
|
||||
)
|
||||
SynonymsDisplay(synonyms = synonyms, loading = false, vocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application), onSynonymAdded = {}, onReload = {})
|
||||
SynonymsDisplay(synonyms = synonyms, loading = false, onSynonymAdded = {}, onReload = {})
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -29,11 +29,12 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.Language
|
||||
import eu.gaudian.translator.model.grammar.CategoryConfig
|
||||
@@ -42,6 +43,7 @@ import eu.gaudian.translator.model.grammar.GrammaticalFeature
|
||||
import eu.gaudian.translator.model.grammar.LanguageConfig
|
||||
import eu.gaudian.translator.model.grammar.formatGrammarDetails
|
||||
import eu.gaudian.translator.utils.Log
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppDialog
|
||||
import eu.gaudian.translator.view.composable.AppTextField
|
||||
import eu.gaudian.translator.view.composable.DialogButton
|
||||
@@ -54,8 +56,9 @@ internal fun GrammarDetailsText(
|
||||
isRevealed: Boolean
|
||||
) {
|
||||
Log.d("GrammarComponents", "GrammarDetailsText called with details: $details, language: $language, isRevealed: $isRevealed")
|
||||
|
||||
val configViewModel: LanguageConfigViewModel = viewModel()
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val configViewModel: LanguageConfigViewModel = hiltViewModel(activity)
|
||||
val allConfigs by configViewModel.configs.collectAsState()
|
||||
val languageConfig = language?.code?.let { allConfigs[it] }
|
||||
val categoryConfig = details?.category?.let { languageConfig?.categories?.get(it) }
|
||||
@@ -88,8 +91,9 @@ internal fun GrammarEditDialog(
|
||||
onSave: (GrammaticalFeature?) -> Unit
|
||||
) {
|
||||
Log.d("GrammarComponents", "GrammarEditDialog called with feature: $feature, language: $language")
|
||||
|
||||
val configViewModel: LanguageConfigViewModel = viewModel()
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val configViewModel: LanguageConfigViewModel = hiltViewModel(activity)
|
||||
val allConfigs by configViewModel.configs.collectAsState()
|
||||
val languageConfig = allConfigs[language.code]
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.vocabulary.card
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -54,7 +53,6 @@ import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.Language
|
||||
@@ -79,7 +77,6 @@ import eu.gaudian.translator.viewmodel.MessageDisplayType
|
||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
|
||||
@Composable
|
||||
@@ -95,12 +92,10 @@ fun VocabularyCard(
|
||||
onDeleteClick: () -> Unit = {},
|
||||
userSpellingAnswer: String? = null,
|
||||
isUserSpellingCorrect: Boolean? = null,
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
) {
|
||||
|
||||
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
|
||||
@@ -463,7 +458,8 @@ private fun GrammarPill(
|
||||
isRevealed: Boolean,
|
||||
onEditGrammarClick: () -> Unit
|
||||
) {
|
||||
val configViewModel: LanguageConfigViewModel = viewModel()
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val configViewModel: LanguageConfigViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val allConfigs by configViewModel.configs.collectAsState()
|
||||
val languageConfig = language?.code?.let { allConfigs[it] }
|
||||
val categoryConfig = wordDetails?.category?.let { languageConfig?.categories?.get(it) }
|
||||
|
||||
@@ -35,14 +35,17 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.TagCategory
|
||||
import eu.gaudian.translator.model.VocabularyCategory
|
||||
import eu.gaudian.translator.ui.theme.ThemePreviews
|
||||
import eu.gaudian.translator.ui.theme.semanticColors
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.SecondaryButton
|
||||
import eu.gaudian.translator.viewmodel.ProgressViewModel
|
||||
@@ -52,7 +55,9 @@ fun CategoryProgressWidget(
|
||||
onCategoryClicked: (VocabularyCategory?) -> Unit,
|
||||
onViewAllClicked: () -> Unit
|
||||
) {
|
||||
val viewModel: ProgressViewModel = ProgressViewModel.getInstance(androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application)
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val viewModel : ProgressViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val categoryProgressList by viewModel.categoryProgressList.collectAsState(initial = emptyList())
|
||||
val selectedCategories by viewModel.selectedCategories.collectAsState(initial = emptySet())
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.gaudian.translator.view.vocabulary.widgets
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -17,24 +16,28 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.ui.theme.semanticColors
|
||||
import eu.gaudian.translator.utils.findActivity
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import okhttp3.internal.platform.PlatformRegistry.applicationContext
|
||||
|
||||
@Composable
|
||||
fun StatusWidget(
|
||||
vocabularyViewModel: VocabularyViewModel = VocabularyViewModel.getInstance(applicationContext as Application),
|
||||
onNavigateToNew: () -> Unit,
|
||||
onNavigateToDuplicates: () -> Unit,
|
||||
onNavigateToFaulty: () -> Unit,
|
||||
onNavigateToNoGrammar: () -> Unit,
|
||||
onNavigateToMissingLanguage: (Int) -> Unit
|
||||
) {
|
||||
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||
val newItemsCount by vocabularyViewModel.newItemsCount.collectAsState()
|
||||
val duplicateCount by vocabularyViewModel.duplicateItemsCount.collectAsState()
|
||||
val faultyItemsCount by vocabularyViewModel.faultyItemsCount.collectAsState()
|
||||
|
||||
@@ -5,8 +5,8 @@ package eu.gaudian.translator.viewmodel
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.model.VocabularyCategory
|
||||
//import eu.gaudian.translator.model.VocabularyDictionary
|
||||
import eu.gaudian.translator.model.repository.VocabularyRepository
|
||||
import eu.gaudian.translator.utils.Log
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -18,35 +18,13 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* TODO: Convert CategoryViewModel to HiltViewModel
|
||||
*
|
||||
* This ViewModel needs to be converted to use Hilt for dependency injection.
|
||||
* Currently it manually instantiates dependencies and uses a singleton pattern.
|
||||
*
|
||||
* Checklist:
|
||||
* - [ ] Add @HiltViewModel annotation to the class
|
||||
* - [ ] Change constructor to use @Inject and inject dependencies:
|
||||
* - VocabularyRepository
|
||||
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
|
||||
* - VocabularyRepository
|
||||
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
|
||||
* - [ ] Create CategoryViewModelEntryPoint interface for accessing the singleton instance
|
||||
* - [ ] Remove manual dependency instantiation from constructor and init block
|
||||
* - [ ] Update all places where CategoryViewModel.getInstance() is called to ensure compatibility
|
||||
* - [ ] Test that the ViewModel works correctly with dependency injection
|
||||
*/
|
||||
class CategoryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
@Volatile private var INSTANCE: CategoryViewModel? = null
|
||||
fun getInstance(application: Application): CategoryViewModel = INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: CategoryViewModel(application).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
|
||||
private val repository: VocabularyRepository = VocabularyRepository.getInstance(application)
|
||||
@HiltViewModel
|
||||
class CategoryViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val repository: VocabularyRepository
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
val categories: StateFlow<List<VocabularyCategory>> = repository.getAllCategoriesFlow()
|
||||
.stateIn(
|
||||
@@ -146,5 +124,4 @@ class CategoryViewModel(application: Application) : AndroidViewModel(application
|
||||
_showEditCategoryDialog.value = show
|
||||
categoryToEdit = categoryId
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,35 +13,22 @@ import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.model.Language
|
||||
import eu.gaudian.translator.utils.CorrectionService
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
/**
|
||||
* TODO: Convert CorrectionViewModel to HiltViewModel
|
||||
*
|
||||
* This ViewModel needs to be converted to use Hilt for dependency injection.
|
||||
* Currently it manually instantiates dependencies in the constructor.
|
||||
*
|
||||
* Checklist:
|
||||
* - [ ] Add @HiltViewModel annotation to the class
|
||||
* - [ ] Change constructor to use @Inject and inject dependencies:
|
||||
* - CorrectionService
|
||||
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
|
||||
* - CorrectionService
|
||||
* - [ ] Remove manual dependency instantiation from constructor
|
||||
* - [ ] Update all places where CorrectionViewModel() is instantiated to use Hilt injection
|
||||
* - [ ] Test that the ViewModel works correctly with dependency injection
|
||||
*/
|
||||
class CorrectionViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class CorrectionViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val correctionService: CorrectionService
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
enum class Tone { NONE, FORMAL, CASUAL, COLLOQUIAL, POLITE, PROFESSIONAL, FRIENDLY, ACADEMIC, CREATIVE }
|
||||
|
||||
val correctionService = CorrectionService(application.applicationContext)
|
||||
|
||||
private val _textFieldValue = MutableStateFlow(TextFieldValue(""))
|
||||
val textFieldValue = _textFieldValue.asStateFlow()
|
||||
|
||||
@@ -150,4 +137,4 @@ class CorrectionViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.model.CategorizationQuestion
|
||||
import eu.gaudian.translator.model.Exercise
|
||||
@@ -35,6 +36,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
sealed class AnswerResult {
|
||||
@@ -85,45 +87,21 @@ sealed class YouTubeExerciseState {
|
||||
data class Error(val message: String) : YouTubeExerciseState()
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Convert ExerciseViewModel to HiltViewModel
|
||||
*
|
||||
* This ViewModel needs to be converted to use Hilt for dependency injection.
|
||||
* Currently it manually instantiates dependencies and uses a singleton pattern.
|
||||
*
|
||||
* Checklist:
|
||||
* - [ ] Add @HiltViewModel annotation to the class
|
||||
* - [ ] Change constructor to use @Inject and inject dependencies:
|
||||
* - ExerciseRepository
|
||||
* - ExerciseService
|
||||
* - VocabularyRepository
|
||||
* - LanguageRepository
|
||||
* - TranslationService
|
||||
* - YouTubeApiService
|
||||
* - YouTubeExerciseService
|
||||
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
|
||||
* - ExerciseRepository
|
||||
* - ExerciseService
|
||||
* - VocabularyRepository
|
||||
* - LanguageRepository
|
||||
* - TranslationService
|
||||
* - YouTubeApiService
|
||||
* - YouTubeExerciseService
|
||||
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
|
||||
* - [ ] Create ExerciseViewModelEntryPoint interface for accessing the singleton instance
|
||||
* - [ ] Remove manual dependency instantiation from constructor and init block
|
||||
* - [ ] Update all places where ExerciseViewModel.getInstance() is called to ensure compatibility
|
||||
* - [ ] Test that the ViewModel works correctly with dependency injection
|
||||
*/
|
||||
class ExerciseViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class ExerciseViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val exerciseRepository: ExerciseRepository,
|
||||
private val exerciseService: ExerciseService,
|
||||
private val vocabularyRepository: VocabularyRepository,
|
||||
private val languageRepository: LanguageRepository,
|
||||
private val translationService: TranslationService,
|
||||
private val youTubeApiService: YouTubeApiService,
|
||||
private val youTubeExerciseService: YouTubeExerciseService
|
||||
) : AndroidViewModel(application) {
|
||||
data class YouTubeSessionParams(val url: String, val sourceLanguage: String?, val targetLanguage: String?)
|
||||
companion object {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Volatile private var INSTANCE: ExerciseViewModel? = null
|
||||
fun getInstance(application: Application): ExerciseViewModel = INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: ExerciseViewModel(application).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
val context = application.applicationContext!!
|
||||
|
||||
private fun normalizeText(input: String?): String {
|
||||
if (input.isNullOrBlank()) return ""
|
||||
@@ -141,18 +119,6 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
|
||||
if (nk.isNotBlank() && nv.isNotBlank()) nk to nv else null
|
||||
}.toMap()
|
||||
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
val context = application.applicationContext!!
|
||||
val exerciseRepository = ExerciseRepository(application.applicationContext)
|
||||
val exerciseService = ExerciseService(application.applicationContext)
|
||||
val vocabularyRepository = VocabularyRepository.getInstance(application.applicationContext)
|
||||
|
||||
// Translation helpers
|
||||
private val languageRepository = LanguageRepository(application.applicationContext)
|
||||
private val translationService = TranslationService(application.applicationContext)
|
||||
private var currentSubtitleTranslationJob: Job? = null
|
||||
|
||||
private val _exercises = MutableStateFlow<List<Exercise>>(emptyList())
|
||||
val exercises: StateFlow<List<Exercise>> = _exercises.asStateFlow()
|
||||
|
||||
@@ -169,7 +135,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
|
||||
val youTubeExerciseState: StateFlow<YouTubeExerciseState> = _youTubeExerciseState.asStateFlow()
|
||||
|
||||
private var currentlyFetchingVideoId: String? = null
|
||||
|
||||
private var currentSubtitleTranslationJob: Job? = null
|
||||
|
||||
init {
|
||||
loadExercisesFromRepository()
|
||||
@@ -412,7 +378,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
|
||||
_youTubeExerciseState.value = YouTubeExerciseState.Loading
|
||||
Log.d("ExerciseViewModel", "State set to Loading for videoId=$videoId")
|
||||
try {
|
||||
val title = YouTubeApiService.getVideoTitle(videoId)
|
||||
val title = youTubeApiService.getVideoTitle(videoId)
|
||||
Log.i("ExerciseViewModel", "Fetched video title: '${title.take(100)}'")
|
||||
|
||||
var available: List<String> = emptyList()
|
||||
@@ -424,7 +390,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
|
||||
delay(2500)
|
||||
}
|
||||
try {
|
||||
available = YouTubeExerciseService.listTranscripts(videoId)
|
||||
available = youTubeExerciseService.listTranscripts(videoId)
|
||||
} catch (e: Exception) {
|
||||
Log.w("ExerciseViewModel", "listTranscripts failed on attempt $attempts", e)
|
||||
}
|
||||
@@ -441,7 +407,7 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
|
||||
Log.d("ExerciseViewModel", "Chosen transcript language: $chosenLang (preferred was $langCode)")
|
||||
|
||||
val finalLang = chosenLang ?: throw Exception("No transcripts available for this video.")
|
||||
val subtitles = YouTubeExerciseService.getTranscript(videoId, finalLang)
|
||||
val subtitles = youTubeExerciseService.getTranscript(videoId, finalLang)
|
||||
Log.i("ExerciseViewModel", "Fetched ${subtitles.size} subtitle lines for $videoId in lang=${finalLang}")
|
||||
|
||||
_youTubeExerciseState.value = YouTubeExerciseState.Success(
|
||||
@@ -616,4 +582,4 @@ class ExerciseViewModel(application: Application) : AndroidViewModel(application
|
||||
// Reset the exercise state to allow restarting
|
||||
_youTubeExerciseState.value = YouTubeExerciseState.Idle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,41 +5,22 @@ package eu.gaudian.translator.viewmodel
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.model.grammar.LanguageConfig
|
||||
import eu.gaudian.translator.utils.Log
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import eu.gaudian.translator.model.repository.LanguageConfigRepository
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* TODO: Convert LanguageConfigViewModel to HiltViewModel
|
||||
*
|
||||
* This ViewModel needs to be converted to use Hilt for dependency injection.
|
||||
* Currently it manually instantiates dependencies in the constructor.
|
||||
*
|
||||
* Checklist:
|
||||
* - [ ] Add @HiltViewModel annotation to the class
|
||||
* - [ ] Change constructor to use @Inject and inject dependencies:
|
||||
* - Application (provided by Hilt)
|
||||
* - [ ] Remove manual dependency instantiation from constructor
|
||||
* - [ ] Update all places where LanguageConfigViewModel() is instantiated to use Hilt injection
|
||||
* - [ ] Test that the ViewModel works correctly with dependency injection
|
||||
*/
|
||||
class LanguageConfigViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class LanguageConfigViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val languageConfigRepository: LanguageConfigRepository
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
private val _configs = MutableStateFlow<Map<String, LanguageConfig>>(emptyMap())
|
||||
val configs = _configs.asStateFlow()
|
||||
|
||||
private val jsonParser = Json { ignoreUnknownKeys = true }
|
||||
|
||||
init {
|
||||
loadAllConfigs()
|
||||
}
|
||||
val configs: StateFlow<Map<String, LanguageConfig>> = languageConfigRepository.configs
|
||||
|
||||
val allWordClasses: StateFlow<List<String>> = configs.map { configsMap ->
|
||||
configsMap.values
|
||||
@@ -48,44 +29,15 @@ class LanguageConfigViewModel(application: Application) : AndroidViewModel(appli
|
||||
.sorted()
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
|
||||
/**
|
||||
* Loads all language configuration files from the "language_configs" asset directory.
|
||||
*/
|
||||
private fun loadAllConfigs() {
|
||||
viewModelScope.launch {
|
||||
val context = getApplication<Application>().applicationContext
|
||||
try {
|
||||
val configFiles = context.assets.list("language_configs") ?: return@launch
|
||||
val loadedConfigs = mutableMapOf<String, LanguageConfig>()
|
||||
|
||||
for (fileName in configFiles) {
|
||||
if (fileName.endsWith(".json")) {
|
||||
val jsonString = context.assets.open("language_configs/$fileName")
|
||||
.bufferedReader()
|
||||
.use { it.readText() }
|
||||
|
||||
val config = jsonParser.decodeFromString<LanguageConfig>(jsonString)
|
||||
loadedConfigs[config.language_code] = config
|
||||
}
|
||||
}
|
||||
_configs.value = loadedConfigs
|
||||
} catch (e: Exception) {
|
||||
// Log the error for debugging, but don't crash the app
|
||||
Log.e("Failed to load language configs: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configuration for a specific language code.
|
||||
*
|
||||
* @param langCode The ISO language code (e.g., "de").
|
||||
* @return The LanguageConfig for the given code, or null if not found.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun getConfigForLanguage(langCode: String): LanguageConfig? {
|
||||
Log.d("Fetching config for language: $langCode")
|
||||
return _configs.value[langCode]
|
||||
return languageConfigRepository.getConfigForLanguage(langCode)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,12 +49,6 @@ class LanguageConfigViewModel(application: Application) : AndroidViewModel(appli
|
||||
* the language code is not found or if the language has no articles defined.
|
||||
*/
|
||||
fun getArticlesForLanguage(langCode: String): Set<String> {
|
||||
return try {
|
||||
val config = _configs.value[langCode]
|
||||
config?.articles?.toSet() ?: emptySet()
|
||||
} catch (e: Exception) {
|
||||
Log.e("Error retrieving articles for '$langCode': ${e.message}")
|
||||
emptySet()
|
||||
}
|
||||
return languageConfigRepository.getArticlesForLanguage(langCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,12 @@ import javax.inject.Inject
|
||||
*/
|
||||
@HiltViewModel
|
||||
class LanguageViewModel @Inject constructor(
|
||||
application: Application
|
||||
application: Application,
|
||||
private val languageRepository: LanguageRepository
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
val languageRepository = LanguageRepository(application)
|
||||
|
||||
private val languageSwitchMutex = Mutex()
|
||||
|
||||
|
||||
// Enabled languages (visible across the app)
|
||||
val allLanguages: StateFlow<List<Language>> = languageRepository.loadLanguages(LanguageListType.ALL)
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
|
||||
@@ -68,8 +66,6 @@ class LanguageViewModel @Inject constructor(
|
||||
|
||||
|
||||
suspend fun getLanguageById(id: Int): Language {
|
||||
|
||||
|
||||
val languages = allLanguages.first { it.isNotEmpty() }
|
||||
|
||||
val language = languages.find { it.nameResId == id }
|
||||
@@ -99,13 +95,13 @@ class LanguageViewModel @Inject constructor(
|
||||
fun switchLanguages() {
|
||||
viewModelScope.launch {
|
||||
languageSwitchMutex.withLock {
|
||||
val source = selectedSourceLanguage.value
|
||||
val target = selectedTargetLanguage.value
|
||||
languageRepository.saveSelectedSourceLanguage(target)
|
||||
languageRepository.saveSelectedTargetLanguage(source)
|
||||
val source = selectedSourceLanguage.value
|
||||
val target = selectedTargetLanguage.value
|
||||
languageRepository.saveSelectedSourceLanguage(target)
|
||||
languageRepository.saveSelectedTargetLanguage(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedSourceLanguage(languageId: Int?) {
|
||||
viewModelScope.launch {
|
||||
@@ -193,4 +189,4 @@ class LanguageViewModel @Inject constructor(
|
||||
languageRepository.editCustomLanguage(languageId, newName, newCode, newRegion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package eu.gaudian.translator.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.application
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.model.VocabularyCategory
|
||||
import eu.gaudian.translator.model.VocabularyStage
|
||||
import eu.gaudian.translator.model.repository.SettingsRepository
|
||||
@@ -26,6 +26,7 @@ import kotlinx.datetime.minus
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import java.text.DateFormatSymbols
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
data class CategoryProgress(
|
||||
@@ -53,38 +54,13 @@ data class StageStats(
|
||||
val itemCount: Int
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* TODO: Convert ProgressViewModel to HiltViewModel
|
||||
*
|
||||
* This ViewModel needs to be converted to use Hilt for dependency injection.
|
||||
* Currently it manually instantiates dependencies and uses a singleton pattern.
|
||||
*
|
||||
* Checklist:
|
||||
* - [ ] Add @HiltViewModel annotation to the class
|
||||
* - [ ] Change constructor to use @Inject and inject dependencies:
|
||||
* - VocabularyRepository
|
||||
* - SettingsRepository
|
||||
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
|
||||
* - VocabularyRepository
|
||||
* - SettingsRepository
|
||||
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
|
||||
* - [ ] Create ProgressViewModelEntryPoint interface for accessing the singleton instance
|
||||
* - [ ] Remove manual dependency instantiation from constructor and init block
|
||||
* - [ ] Update all places where ProgressViewModel.getInstance() is called to ensure compatibility
|
||||
* - [ ] Test that the ViewModel works correctly with dependency injection
|
||||
*/
|
||||
class ProgressViewModel(application: Application) : AndroidViewModel(application) {
|
||||
companion object {
|
||||
@Volatile private var INSTANCE: ProgressViewModel? = null
|
||||
fun getInstance(application: Application): ProgressViewModel = INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: ProgressViewModel(application).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val vocabularyRepository = VocabularyRepository.getInstance(application.applicationContext)
|
||||
val settingsRepository = SettingsRepository(application.applicationContext)
|
||||
@Suppress("SameParameterValue")
|
||||
@HiltViewModel
|
||||
class ProgressViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val vocabularyRepository: VocabularyRepository,
|
||||
private val settingsRepository: SettingsRepository
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
// Single-flight + coalescing scheduler for refresh()
|
||||
private val refreshMutex = Mutex()
|
||||
@@ -204,7 +180,6 @@ class ProgressViewModel(application: Application) : AndroidViewModel(application
|
||||
}
|
||||
|
||||
suspend fun getWeeklyActivityStats(): List<WeeklyActivityStat> {
|
||||
val vocabularyRepository = VocabularyRepository.getInstance(application)
|
||||
val today = kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||
|
||||
// 1. Create a list of the last 7 dates in chronological order.
|
||||
@@ -314,4 +289,4 @@ class ProgressViewModel(application: Application) : AndroidViewModel(application
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package eu.gaudian.translator.viewmodel
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.utils.Log
|
||||
import eu.gaudian.translator.utils.StatusAction
|
||||
import eu.gaudian.translator.utils.StatusMessageService
|
||||
@@ -17,6 +18,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.LinkedList
|
||||
import java.util.Queue
|
||||
import javax.inject.Inject
|
||||
|
||||
enum class MessageAction {
|
||||
NAVIGATE_TO_API_KEYS
|
||||
@@ -43,31 +45,11 @@ enum class MessageDisplayType(val priority: Int) {
|
||||
ACTIONABLE_ERROR(5)
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Convert StatusViewModel to HiltViewModel
|
||||
*
|
||||
* This ViewModel needs to be converted to use Hilt for dependency injection.
|
||||
* Currently it manually instantiates dependencies and uses a singleton pattern.
|
||||
*
|
||||
* Checklist:
|
||||
* - [ ] Add @HiltViewModel annotation to the class
|
||||
* - [ ] Change constructor to use @Inject and inject dependencies:
|
||||
* - StatusMessageService
|
||||
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
|
||||
* - StatusMessageService
|
||||
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
|
||||
* - [ ] Create StatusViewModelEntryPoint interface for accessing the singleton instance
|
||||
* - [ ] Remove manual dependency instantiation from constructor and init block
|
||||
* - [ ] Update all places where StatusViewModel.getInstance() is called to ensure compatibility
|
||||
* - [ ] Test that the ViewModel works correctly with dependency injection
|
||||
*/
|
||||
class StatusViewModel(application: Application) : AndroidViewModel(application) {
|
||||
companion object {
|
||||
@Volatile private var INSTANCE: StatusViewModel? = null
|
||||
fun getInstance(application: Application): StatusViewModel = INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: StatusViewModel(application).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
@HiltViewModel
|
||||
class StatusViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val statusMessageService: StatusMessageService
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
private val messageQueue: Queue<Pair<String, MessageDisplayType>> = LinkedList()
|
||||
private var messageDisplayJob: Job? = null
|
||||
@@ -79,7 +61,7 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
StatusMessageService.actions.collect { action ->
|
||||
statusMessageService.actions.collect { action ->
|
||||
handleAction(action)
|
||||
}
|
||||
}
|
||||
@@ -100,12 +82,10 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
|
||||
}
|
||||
|
||||
fun showApiKeyMissingMessage() = viewModelScope.launch {
|
||||
StatusMessageService.trigger(
|
||||
StatusAction.ShowActionableMessage(
|
||||
text = "API Key is missing or invalid.",
|
||||
type = MessageDisplayType.ACTIONABLE_ERROR,
|
||||
action = MessageAction.NAVIGATE_TO_API_KEYS
|
||||
)
|
||||
statusMessageService.showActionableMessage(
|
||||
text = "API Key is missing or invalid.",
|
||||
type = MessageDisplayType.ACTIONABLE_ERROR,
|
||||
action = MessageAction.NAVIGATE_TO_API_KEYS
|
||||
)
|
||||
}
|
||||
|
||||
@@ -120,43 +100,43 @@ class StatusViewModel(application: Application) : AndroidViewModel(application)
|
||||
}
|
||||
|
||||
fun showPermanentMessage(message: String, type: MessageDisplayType) = viewModelScope.launch {
|
||||
StatusMessageService.trigger(StatusAction.ShowPermanentMessage(message, type))
|
||||
statusMessageService.showPermanentMessage(message, type)
|
||||
}
|
||||
|
||||
fun cancelPermanentMessage() = viewModelScope.launch {
|
||||
StatusMessageService.trigger(StatusAction.CancelPermanentMessage)
|
||||
statusMessageService.cancelPermanentMessage()
|
||||
}
|
||||
|
||||
fun performLoadingOperation(block: suspend () -> Unit) = viewModelScope.launch {
|
||||
StatusMessageService.trigger(StatusAction.PerformLoadingOperation(block))
|
||||
statusMessageService.trigger(StatusAction.PerformLoadingOperation(block))
|
||||
}
|
||||
|
||||
fun cancelLoadingOperation() = viewModelScope.launch {
|
||||
StatusMessageService.trigger(StatusAction.CancelLoadingOperation)
|
||||
statusMessageService.trigger(StatusAction.CancelLoadingOperation)
|
||||
}
|
||||
|
||||
fun showInfoMessage(message: String, timeoutInSeconds: Int = 3) = viewModelScope.launch {
|
||||
StatusMessageService.trigger(StatusAction.ShowMessage(message, MessageDisplayType.INFO, timeoutInSeconds))
|
||||
statusMessageService.showInfoMessage(message, timeoutInSeconds)
|
||||
}
|
||||
|
||||
fun showLoadingMessage(message: String, timeoutInSeconds: Int = 0) = viewModelScope.launch { // Default timeout 0 for indefinite
|
||||
StatusMessageService.trigger(StatusAction.ShowMessage(message, MessageDisplayType.LOADING, timeoutInSeconds))
|
||||
statusMessageService.showLoadingMessage(message, timeoutInSeconds)
|
||||
}
|
||||
|
||||
fun showErrorMessage(message: String, timeoutInSeconds: Int = 5) = viewModelScope.launch { // Default timeout 5 for errors
|
||||
StatusMessageService.trigger(StatusAction.ShowMessage(message, MessageDisplayType.ERROR, timeoutInSeconds))
|
||||
statusMessageService.showErrorMessage(message, timeoutInSeconds)
|
||||
}
|
||||
|
||||
fun showSuccessMessage(message: String, timeoutInSeconds: Int = 3) = viewModelScope.launch {
|
||||
StatusMessageService.trigger(StatusAction.ShowMessage(message, MessageDisplayType.SUCCESS, timeoutInSeconds))
|
||||
statusMessageService.showSuccessMessage(message, timeoutInSeconds)
|
||||
}
|
||||
|
||||
fun hideMessageBar() = viewModelScope.launch {
|
||||
StatusMessageService.trigger(StatusAction.HideMessageBar)
|
||||
statusMessageService.hideMessageBar()
|
||||
}
|
||||
|
||||
fun cancelAllMessages() = viewModelScope.launch {
|
||||
StatusMessageService.trigger(StatusAction.CancelAllMessages)
|
||||
statusMessageService.cancelAllMessages()
|
||||
}
|
||||
|
||||
private fun cancelPermanentMessageInternal() {
|
||||
|
||||
@@ -5,6 +5,7 @@ package eu.gaudian.translator.viewmodel
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.model.LanguageModel
|
||||
import eu.gaudian.translator.model.TranslationHistoryItem
|
||||
import eu.gaudian.translator.model.repository.ApiRepository
|
||||
@@ -20,33 +21,17 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* TODO: Convert TranslationViewModel to HiltViewModel
|
||||
*
|
||||
* This ViewModel needs to be converted to use Hilt for dependency injection.
|
||||
* Currently it manually instantiates dependencies in the constructor.
|
||||
*
|
||||
* Checklist:
|
||||
* - [ ] Add @HiltViewModel annotation to the class
|
||||
* - [ ] Change constructor to use @Inject and inject dependencies:
|
||||
* - TranslationService
|
||||
* - ApiRepository
|
||||
* - TextToSpeechHelper (if needed)
|
||||
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
|
||||
* - TranslationService
|
||||
* - ApiRepository
|
||||
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
|
||||
* - [ ] Create TranslationViewModelEntryPoint interface for accessing the singleton instance
|
||||
* - [ ] Remove manual dependency instantiation from constructor and init block
|
||||
* - [ ] Update all places where TranslationViewModel.getInstance() is called to ensure compatibility
|
||||
* - [ ] Test that the ViewModel works correctly with dependency injection
|
||||
*/
|
||||
class TranslationViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class TranslationViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val translationService: TranslationService,
|
||||
private val apiRepository: ApiRepository,
|
||||
val languageRepository: LanguageRepository
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
// For back/forward navigation of history in the UI (like editors)
|
||||
val languageRepository = LanguageRepository(application)
|
||||
|
||||
private val _historyCursor = MutableStateFlow(-1)
|
||||
|
||||
private val _canGoBack = MutableStateFlow(false)
|
||||
@@ -57,9 +42,6 @@ class TranslationViewModel(application: Application) : AndroidViewModel(applicat
|
||||
|
||||
val historyCursor: StateFlow<Int> get() = _historyCursor
|
||||
|
||||
private val translationService = TranslationService(application)
|
||||
private val apiRepository = ApiRepository(application)
|
||||
|
||||
private val _inputText = MutableStateFlow("")
|
||||
val inputText: StateFlow<String> get() = _inputText
|
||||
|
||||
@@ -341,4 +323,4 @@ class TranslationViewModel(application: Application) : AndroidViewModel(applicat
|
||||
}
|
||||
updateBackForwardState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.model.VocabularyItem
|
||||
import eu.gaudian.translator.model.repository.LanguageConfigRepository
|
||||
import eu.gaudian.translator.model.repository.LanguageRepository
|
||||
import eu.gaudian.translator.model.repository.VocabularyRepository
|
||||
import eu.gaudian.translator.utils.Log
|
||||
import eu.gaudian.translator.utils.StringHelper
|
||||
@@ -54,14 +56,11 @@ data class ExerciseResults(
|
||||
@HiltViewModel
|
||||
class VocabularyExerciseViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val vocabularyRepository: VocabularyRepository,
|
||||
private val languageConfigRepository: LanguageConfigRepository,
|
||||
private val languageRepository: LanguageRepository
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
private val vocabularyRepository = VocabularyRepository.getInstance(application)
|
||||
|
||||
|
||||
private val languageConfigViewModel = LanguageConfigViewModel(application)
|
||||
private val languageViewModel = LanguageViewModel(application)
|
||||
|
||||
private val _exerciseState = MutableStateFlow<VocabularyExerciseState?>(null)
|
||||
val exerciseState: StateFlow<VocabularyExerciseState?> = _exerciseState.asStateFlow()
|
||||
|
||||
@@ -232,7 +231,7 @@ class VocabularyExerciseViewModel @Inject constructor(
|
||||
}
|
||||
} else {
|
||||
// Item matches, determine if we need to switch
|
||||
// Switch if origin is specified and it's not the first language, or if target is specified and it IS the first language
|
||||
// if origin is specified, and it's not the first language, or if target is specified and it IS the first language
|
||||
(config.originLanguageId != null && config.originLanguageId != itemToUse.languageFirstId) ||
|
||||
(config.originLanguageId == null && config.targetLanguageId != null && config.targetLanguageId == itemToUse.languageFirstId)
|
||||
}
|
||||
@@ -355,10 +354,11 @@ class VocabularyExerciseViewModel @Inject constructor(
|
||||
is VocabularyExerciseState.Spelling -> {
|
||||
val userAnswer = (answer as String).trim()
|
||||
val languageId = if (state.isSwitched) state.item.languageFirstId else state.item.languageSecondId
|
||||
val language = languageViewModel.getLanguageById(languageId ?: 0)
|
||||
val language = languageRepository.getLanguageById(languageId ?: 0)
|
||||
?: return@launch
|
||||
|
||||
// Get articles for the language
|
||||
val articles = languageConfigViewModel.getArticlesForLanguage(language.code)
|
||||
val articles = languageConfigRepository.getArticlesForLanguage(language.code)
|
||||
|
||||
// Normalize and split possible answers using helper
|
||||
val normalizedCorrectAnswer = StringHelper.normalizeAnswer(correctAnswer)
|
||||
@@ -484,6 +484,4 @@ class VocabularyExerciseViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.gaudian.translator.model.CardSet
|
||||
import eu.gaudian.translator.model.Language
|
||||
import eu.gaudian.translator.model.VocabularyCategory
|
||||
@@ -18,11 +19,14 @@ import eu.gaudian.translator.model.VocabularyGrammarDetails
|
||||
import eu.gaudian.translator.model.VocabularyItem
|
||||
import eu.gaudian.translator.model.VocabularyStage
|
||||
import eu.gaudian.translator.model.jsonParser
|
||||
import eu.gaudian.translator.model.repository.LanguageConfigRepository
|
||||
import eu.gaudian.translator.model.repository.LanguageListType
|
||||
import eu.gaudian.translator.model.repository.LanguageRepository
|
||||
import eu.gaudian.translator.model.repository.VocabularyFileSaver
|
||||
import eu.gaudian.translator.model.repository.VocabularyRepository
|
||||
import eu.gaudian.translator.utils.Log
|
||||
import eu.gaudian.translator.utils.StatusAction
|
||||
import eu.gaudian.translator.utils.StatusMessageService
|
||||
import eu.gaudian.translator.utils.StringHelper
|
||||
import eu.gaudian.translator.utils.VocabularyService
|
||||
import eu.gaudian.translator.utils.dictionary.DictionaryService
|
||||
@@ -47,38 +51,20 @@ import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.measureTimeMillis
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
/**
|
||||
* TODO: Convert VocabularyViewModel to HiltViewModel
|
||||
*
|
||||
* This ViewModel needs to be converted to use Hilt for dependency injection.
|
||||
* Currently it manually instantiates dependencies in the constructor and companion object.
|
||||
*
|
||||
* Checklist:
|
||||
* - [ ] Add @HiltViewModel annotation to the class
|
||||
* - [ ] Change constructor to use @Inject and inject dependencies:
|
||||
* - ApiManager
|
||||
* - ApiRepository
|
||||
* - SettingsRepository
|
||||
* - ApiLogRepository
|
||||
* - VocabularyRepository
|
||||
* - LanguageRepository
|
||||
* - VocabularyService
|
||||
* - StatusViewModel (consider if this should be injected or accessed differently)
|
||||
* - LanguageConfigViewModel (consider if this should be injected or accessed differently)
|
||||
* - [ ] Create/update RepositoryModule.kt to provide singleton instances of:
|
||||
* - VocabularyRepository
|
||||
* - LanguageRepository
|
||||
* - VocabularyService
|
||||
* - [ ] Modify companion object getInstance() method to use Hilt's EntryPoint system
|
||||
* - [ ] Create VocabularyViewModelEntryPoint interface for accessing the singleton instance
|
||||
* - [ ] Remove manual dependency instantiation from constructor and init block
|
||||
* - [ ] Update all places where VocabularyViewModel.getInstance() is called to ensure compatibility
|
||||
* - [ ] Test that the ViewModel works correctly with dependency injection
|
||||
*/
|
||||
class VocabularyViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class VocabularyViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val vocabularyRepository: VocabularyRepository,
|
||||
private val languageRepository: LanguageRepository,
|
||||
private val languageConfigRepository: LanguageConfigRepository,
|
||||
private val vocabularyItemService: VocabularyService,
|
||||
private val dictionaryService: DictionaryService,
|
||||
private val statusService: StatusMessageService,
|
||||
) : AndroidViewModel(application) {
|
||||
fun extractUniqueWords(text: String): List<String> {
|
||||
if (text.isBlank()) return emptyList()
|
||||
val words = text.lowercase()
|
||||
@@ -95,48 +81,35 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
val src = languageRepository.loadSelectedSourceLanguage().first()
|
||||
val tgt = languageRepository.loadSelectedTargetLanguage().first()
|
||||
if (src == null || tgt == null) {
|
||||
statusViewModel.showErrorMessage("Source and target languages must be selected.")
|
||||
statusService.showErrorMessage("Source and target languages must be selected.")
|
||||
return@launch
|
||||
}
|
||||
val words = extractUniqueWords(text)
|
||||
if (words.isEmpty()) {
|
||||
statusViewModel.showErrorMessage("No words found in the provided text.")
|
||||
statusService.showErrorMessage("No words found in the provided text.")
|
||||
return@launch
|
||||
}
|
||||
_isGenerating.value = true
|
||||
statusViewModel.showLoadingMessage("Translating ${words.size} words…")
|
||||
statusService.showLoadingMessage("Translating ${words.size} words…")
|
||||
val result = vocabularyItemService.translateWordsBatch(words, src, tgt)
|
||||
result.onSuccess { items ->
|
||||
_generatedVocabularyItems.value = items
|
||||
statusViewModel.cancelLoadingOperation()
|
||||
statusService.trigger(StatusAction.CancelLoadingOperation)
|
||||
}.onFailure { ex ->
|
||||
statusViewModel.showErrorMessage("Failed to translate words: ${ex.message}")
|
||||
statusService.showErrorMessage("Failed to translate words: ${ex.message}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Unexpected error: ${e.message}")
|
||||
statusService.showErrorMessage("Unexpected error: ${e.message}")
|
||||
} finally {
|
||||
_isGenerating.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
@Volatile private var INSTANCE: VocabularyViewModel? = null
|
||||
fun getInstance(application: Application): VocabularyViewModel = INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: VocabularyViewModel(application).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("PrivatePropertyName")
|
||||
private val TAG = "VocabularyViewModel"
|
||||
|
||||
|
||||
val statusViewModel = StatusViewModel.getInstance(application)
|
||||
val languageConfigViewModel = LanguageConfigViewModel(application)
|
||||
val vocabularyRepository = VocabularyRepository.getInstance(application)
|
||||
val languageRepository = LanguageRepository(application)
|
||||
|
||||
private val vocabularyItemService = VocabularyService(application)
|
||||
|
||||
|
||||
val vocabularyItems: StateFlow<List<VocabularyItem>> = vocabularyRepository.getAllVocabularyItemsFlow()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
|
||||
@@ -171,8 +144,6 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
val stageMapping: StateFlow<Map<Int, VocabularyStage>> = vocabularyRepository.loadStageMapping()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap())
|
||||
|
||||
val dictionaryService = DictionaryService(application)
|
||||
|
||||
private val _cardSet = MutableStateFlow<CardSet?>(null)
|
||||
val cardSet: StateFlow<CardSet?> = _cardSet.asStateFlow()
|
||||
|
||||
@@ -223,7 +194,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
.values // We only need the groups of items, not the keys
|
||||
.filter { group -> group.size > 1 } // A group with more than one item signifies duplicates
|
||||
.flatten() // Convert the list of duplicate groups into a a single list of all duplicate items
|
||||
.flatten() // Convert the list of duplicate groups into a single list of all duplicate items
|
||||
}
|
||||
.flowOn(Dispatchers.Default) // Perform this potentially heavy computation on a background thread
|
||||
.stateIn(
|
||||
@@ -300,7 +271,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
Log.d(TAG, "Successfully added vocabulary items to category in ${duration}ms")
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error in addVocabularyItemToCategory: ${e.message}")
|
||||
statusService.showErrorMessage("Error in addVocabularyItemToCategory: ${e.message}")
|
||||
Log.e(TAG, "Error in addVocabularyItemToCategory: ${e.message}")
|
||||
}
|
||||
}
|
||||
@@ -325,7 +296,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
Log.d(TAG, "Successfully removed vocabulary items from category in ${duration}ms")
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error removing items from category: ${e.message}")
|
||||
statusService.showErrorMessage("Error removing items from category: ${e.message}")
|
||||
Log.e(TAG, "Error in removeVocabularyItemsFromCategory: ${e.message}")
|
||||
}
|
||||
}
|
||||
@@ -338,7 +309,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
vocabularyRepository.changeVocabularyItemsStage(vocabularyItems, stage)
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error in addVocabularyItemToStage: ${e.message}")
|
||||
statusService.showErrorMessage("Error in addVocabularyItemToStage: ${e.message}")
|
||||
Log.e(TAG, "Error in addVocabularyItemToStage: ${e.message}")
|
||||
}
|
||||
}
|
||||
@@ -356,7 +327,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
"Successfully added ${items.size} new vocabulary items in ${duration}ms"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error adding items: ${e.message}")
|
||||
statusService.showErrorMessage("Error adding items: ${e.message}")
|
||||
Log.e(TAG, "Error adding items: ${e.message}")
|
||||
}
|
||||
}
|
||||
@@ -373,7 +344,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
Log.d(TAG, "Successfully deleted vocabulary items in ${duration}ms")
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error deleting items: ${e.message}")
|
||||
statusService.showErrorMessage("Error deleting items: ${e.message}")
|
||||
Log.e(TAG, "Error deleting item: ${e.message}")
|
||||
}
|
||||
}
|
||||
@@ -389,7 +360,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
Log.d(TAG, "Successfully edited vocabulary item in ${duration}ms")
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error in editVocabularyItem: ${e.message}")
|
||||
statusService.showErrorMessage("Error in editVocabularyItem: ${e.message}")
|
||||
Log.e(TAG, "Error in editVocabularyItem: ${e.message}")
|
||||
}
|
||||
}
|
||||
@@ -445,7 +416,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
// Placeholder: For now, this just deletes the new item.
|
||||
// A full implementation would merge stages, categories, etc., and update the existing item based on the rules.
|
||||
viewModelScope.launch {
|
||||
statusViewModel.showSuccessMessage("Items merged!", 2)
|
||||
statusService.showSuccessMessage("Items merged!", 2)
|
||||
deleteVocabularyItemsById(listOf(newItem.id))
|
||||
}
|
||||
}
|
||||
@@ -469,7 +440,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
Log.d(TAG, "deleteData of type $type completed in ${duration}ms")
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error in deleteData: ${e.message}")
|
||||
statusService.showErrorMessage("Error in deleteData: ${e.message}")
|
||||
Log.e(TAG, "Error in deleteData: ${e.message}")
|
||||
}
|
||||
}
|
||||
@@ -723,7 +694,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
) {
|
||||
Log.d(TAG, "Loading card set with languages: $languages, categories: ${categories?.size}, stages: ${stages?.size}")
|
||||
viewModelScope.launch {
|
||||
statusViewModel.showLoadingMessage("Loading card set")
|
||||
statusService.showLoadingMessage("Loading card set")
|
||||
|
||||
try {
|
||||
// Step 1: Wait for vocabulary items to be loaded
|
||||
@@ -753,7 +724,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
if (stageMappingMap.isEmpty()) {
|
||||
Log.w(TAG, "loadCardSet: Stage mapping still empty after $maxAttempts attempts")
|
||||
statusViewModel.showErrorMessage("Failed to initialize stage mappings")
|
||||
statusService.showErrorMessage("Failed to initialize stage mappings")
|
||||
_cardSet.value = null
|
||||
return@launch
|
||||
} else {
|
||||
@@ -795,15 +766,15 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
Log.d(TAG, "No items found for the specified filters")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error loading card set: ${e.message}")
|
||||
statusService.showErrorMessage("Error loading card set: ${e.message}")
|
||||
_cardSet.value = null
|
||||
Log.e(TAG, "Error in loadCardSet: ${e.message}")
|
||||
}
|
||||
|
||||
statusViewModel.hideMessageBar()
|
||||
statusService.hideMessageBar()
|
||||
if (_cardSet.value == null) {
|
||||
statusViewModel.cancelAllMessages()
|
||||
statusViewModel.showErrorMessage("No cards found for the specified filter", 3)
|
||||
statusService.cancelAllMessages()
|
||||
statusService.showErrorMessage("No cards found for the specified filter", 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -818,7 +789,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
Log.d(TAG, "deleteVocabularyItemsByCategory for ID $categoryID took ${duration}ms")
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error in deleteVocabularyItemsByCategory: ${e.message}")
|
||||
statusService.showErrorMessage("Error in deleteVocabularyItemsByCategory: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -858,7 +829,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
fun saveRepositoryState() {
|
||||
if (!::saveFileLauncher.isInitialized) {
|
||||
statusViewModel.showErrorMessage("Save File Launcher not initialized.")
|
||||
statusService.showErrorMessage("Save File Launcher not initialized.")
|
||||
return
|
||||
}
|
||||
val intent = fileSaver.createSaveDocumentIntent(fileSaver.generateFilename())
|
||||
@@ -867,7 +838,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
fun saveCategory(categoryId: Int) {
|
||||
if (!::saveFileLauncher.isInitialized) {
|
||||
statusViewModel.showErrorMessage("Save File Launcher not initialized.")
|
||||
statusService.showErrorMessage("Save File Launcher not initialized.")
|
||||
return
|
||||
}
|
||||
val filename = fileSaver.generateFilenameForCategory(categoryId)
|
||||
@@ -897,11 +868,11 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
}
|
||||
} ?: run {
|
||||
statusViewModel.showErrorMessage("Error: No URI received from file picker.")
|
||||
statusService.showErrorMessage("Error: No URI received from file picker.")
|
||||
Log.e(TAG, "handleSaveResult: No URI received")
|
||||
}
|
||||
} else {
|
||||
statusViewModel.showErrorMessage("File save cancelled or failed.")
|
||||
statusService.showErrorMessage("File save cancelled or failed.")
|
||||
Log.d(TAG, "handleSaveResult: Save cancelled or failed")
|
||||
}
|
||||
}
|
||||
@@ -916,10 +887,10 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
vocabularyRepository.introduceVocabularyItems(vocabularyItems)
|
||||
vocabularyRepository.cleanDuplicates()
|
||||
}
|
||||
statusViewModel.showSuccessMessage("Vocabulary items imported successfully.")
|
||||
statusService.showSuccessMessage("Vocabulary items imported successfully.")
|
||||
Log.d(TAG, "Vocabulary import process took ${duration}ms")
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error importing vocabulary items: ${e.message}")
|
||||
statusService.showErrorMessage("Error importing vocabulary items: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -928,9 +899,9 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
vocabularyRepository.wipeRepository()
|
||||
statusViewModel.showErrorMessage("All repository data deleted.")
|
||||
statusService.showErrorMessage("All repository data deleted.")
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Failed to wipe repository: $e.message}")
|
||||
statusService.showErrorMessage("Failed to wipe repository: $e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1075,7 +1046,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
statusViewModel.showLoadingMessage("Fetching grammar for ${items.size} items...")
|
||||
statusService.showLoadingMessage("Fetching grammar for ${items.size} items...")
|
||||
Log.d(TAG, "Attempting to fetch grammar details for a list of ${items.size} items.")
|
||||
|
||||
try {
|
||||
@@ -1089,8 +1060,8 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
if (StringHelper.isSentenceStrict(item.wordFirst) || StringHelper.isSentenceStrict(item.wordSecond)) {
|
||||
item // Skip article removal for sentences
|
||||
} else {
|
||||
val articlesLangFirst = languagesMap[item.languageFirstId]?.code?.let { languageConfigViewModel.getArticlesForLanguage(it) } ?: emptySet()
|
||||
val articlesLangSecond = languagesMap[item.languageSecondId]?.code?.let { languageConfigViewModel.getArticlesForLanguage(it) } ?: emptySet()
|
||||
val articlesLangFirst = languagesMap[item.languageFirstId]?.code?.let { languageConfigRepository.getArticlesForLanguage(it) } ?: emptySet()
|
||||
val articlesLangSecond = languagesMap[item.languageSecondId]?.code?.let { languageConfigRepository.getArticlesForLanguage(it) } ?: emptySet()
|
||||
|
||||
val allArticles = (articlesLangFirst + articlesLangSecond)
|
||||
|
||||
@@ -1110,16 +1081,16 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
// 5. Persist the updated items back to the database.
|
||||
vocabularyRepository.updateVocabularyItems(updatedItems)
|
||||
statusViewModel.cancelLoadingOperation()
|
||||
statusViewModel.showSuccessMessage("Grammar details updated!")
|
||||
statusService.trigger(StatusAction.CancelLoadingOperation)
|
||||
statusService.showSuccessMessage("Grammar details updated!")
|
||||
|
||||
}.onFailure { exception ->
|
||||
Log.e(TAG, "Failed to fetch grammar details for list: ${exception.message}")
|
||||
statusViewModel.showErrorMessage("Could not retrieve grammar details.")
|
||||
statusService.showErrorMessage("Could not retrieve grammar details.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "An unexpected error occurred in fetchAndApplyGrammaticalDetailsForList: ${e.message}")
|
||||
statusViewModel.showErrorMessage("An unexpected error occurred.")
|
||||
statusService.showErrorMessage("An unexpected error occurred.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1164,7 +1135,7 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
Log.d(TAG, "removeArticles: No articles found in item ${item.id}, no update needed.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
statusViewModel.showErrorMessage("Error removing articles: ${e.message}")
|
||||
statusService.showErrorMessage("Error removing articles: ${e.message}")
|
||||
Log.e(TAG, "Error in removeArticles: ${e.message}")
|
||||
}
|
||||
}
|
||||
@@ -1267,14 +1238,14 @@ class VocabularyViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
if (itemsToUpdate.isNotEmpty()) {
|
||||
vocabularyRepository.updateVocabularyItems(itemsToUpdate)
|
||||
statusViewModel.showSuccessMessage("Language ID updated for ${itemsToUpdate.size} items.")
|
||||
statusService.showSuccessMessage("Language ID updated for ${itemsToUpdate.size} items.")
|
||||
Log.d(TAG, "Successfully updated ${itemsToUpdate.size} items.")
|
||||
} else {
|
||||
Log.d(TAG, "No items found with language ID $oldId to update.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val errorMessage = "Error replacing language ID: ${e.message}"
|
||||
statusViewModel.showErrorMessage(errorMessage)
|
||||
statusService.showErrorMessage(errorMessage)
|
||||
Log.e(TAG, errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,16 +21,6 @@ class SettingsViewModelTest {
|
||||
androidx.lifecycle.AndroidViewModel::class.java.isAssignableFrom(viewModelClass))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SettingsViewModel companion object has getInstance method`() {
|
||||
// Given - Get the companion object
|
||||
val companionObject = SettingsViewModel.Companion::class.java
|
||||
|
||||
// Then - Should have getInstance method
|
||||
assertTrue("Companion should have getInstance method",
|
||||
companionObject.methods.any { it.name == "getInstance" })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SettingsViewModel has expected constructor parameters`() {
|
||||
// Given - Get the ViewModel class
|
||||
|
||||
Reference in New Issue
Block a user