diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/CategoryDropdown.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/CategoryDropdown.kt index 5a9ec56..4d98e6d 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/CategoryDropdown.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/CategoryDropdown.kt @@ -15,29 +15,34 @@ 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 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.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.model.VocabularyFilter import eu.gaudian.translator.ui.theme.ThemePreviews +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 import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppOutlinedButton import eu.gaudian.translator.view.composable.AppOutlinedTextField +import eu.gaudian.translator.viewmodel.CategoryViewModel /** @@ -95,7 +100,7 @@ fun CategoryDropdownContent( text = when { state.selectedCategories.isEmpty() -> stringResource(R.string.text_select_category) state.selectedCategories.size == 1 -> state.selectedCategories.first()?.name - ?: stringResource(R.string.text_none) + ?: stringResource(R.string.label_no_category) else -> stringResource(R.string.text_2d_categories_selected, state.selectedCategories.size) }, modifier = Modifier.weight(1f), @@ -139,7 +144,7 @@ fun CategoryDropdownContent( ) Spacer(modifier = Modifier.width(8.dp)) } - Text(stringResource(R.string.text_none)) + Text(stringResource(R.string.label_no_category)) } }, onClick = { @@ -270,13 +275,13 @@ fun CategoryDropdownContent( */ @Composable fun CategoryDropdown( + modifier: Modifier = Modifier, initialCategoryId: Int? = null, onCategorySelected: (List) -> Unit, noneSelectable: Boolean? = true, multipleSelectable: Boolean = false, onlyLists: Boolean = false, addCategory: Boolean = false, - modifier: Modifier = Modifier, ) { var expanded by remember { mutableStateOf(false) } var selectedCategories by remember { @@ -284,9 +289,12 @@ fun CategoryDropdown( } var newCategoryName by remember { mutableStateOf("") } - // For production use, this would come from ViewModel - // For preview, we'll use empty list or pass via state - val categories by remember { mutableStateOf(emptyList()) } + val activity = LocalContext.current.findActivity() + val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity) + val categories by categoryViewModel.categories.collectAsState(initial = emptyList()) + + + // Find initial category val initialCategory = remember(categories, initialCategoryId) { diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/CategorySelectionDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/CategorySelectionDialog.kt index d719128..f414baa 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/CategorySelectionDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/CategorySelectionDialog.kt @@ -8,9 +8,6 @@ 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 -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -18,7 +15,6 @@ 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.model.VocabularyCategory import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.view.composable.AppDialog @@ -34,9 +30,6 @@ fun CategorySelectionDialog( val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity) val categories by categoryViewModel.categories.collectAsState(initial = emptyList()) - var selectedCategories by remember { mutableStateOf>(emptyList()) } - var newCategoryName by remember { mutableStateOf("") } - var expanded by remember { mutableStateOf(false) } AppDialog( onDismissRequest = onDismissRequest, @@ -45,21 +38,8 @@ fun CategorySelectionDialog( } ) { // Dropdown button and menu - CategoryDropdownContent( - state = CategoryDropdownState( - expanded = expanded, - selectedCategories = selectedCategories, - newCategoryName = newCategoryName, - categories = categories, - ), - onExpand = { isExpanded -> expanded = isExpanded }, - onCategorySelected = { selectedCategories = it }, - onNewCategoryNameChange = { newCategoryName = it }, - onAddCategory = { name -> - val newCategory = TagCategory(id = 0, name = name.trim()) - categoryViewModel.createCategory(newCategory) - newCategoryName = "" - }, + CategoryDropdown( + onCategorySelected = onCategorySelected, noneSelectable = false, multipleSelectable = true, onlyLists = true, @@ -79,10 +59,11 @@ fun CategorySelectionDialog( DialogButton( onClick = { - onCategorySelected(selectedCategories) + // The selected categories are handled by CategoryDropdown's internal state + // and passed to onCategorySelected callback onDismissRequest() }, - enabled = selectedCategories.isNotEmpty() + enabled = true // Always enabled since CategoryDropdown handles validation ) { Text(stringResource(R.string.label_confirm)) } diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/StartExerciseDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/StartExerciseDialog.kt index 78f7354..a12569a 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/StartExerciseDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/StartExerciseDialog.kt @@ -55,9 +55,8 @@ fun StartExerciseDialog( // Map displayed Language to its DB id (lid) using position mapping from load var languageIdMap by remember { mutableStateOf>(emptyMap()) } var selectedLanguages by remember { mutableStateOf>(emptyList()) } - var selectedCategories by remember { mutableStateOf>(emptyList()) } var selectedStages by remember { mutableStateOf>(emptyList()) } - var expanded by remember { mutableStateOf(false) } + var selectedCategories by remember { mutableStateOf>(emptyList()) } LaunchedEffect(Unit) { coroutineScope.launch { @@ -87,19 +86,10 @@ fun StartExerciseDialog( }, languages ) - CategoryDropdownContent( - state = CategoryDropdownState( - expanded = expanded, - selectedCategories = selectedCategories, - newCategoryName = "", - categories = categories, - ), - onExpand = { isExpanded -> expanded = isExpanded }, + CategoryDropdown( onCategorySelected = { cats -> selectedCategories = cats.filterIsInstance() }, - onNewCategoryNameChange = {}, - onAddCategory = {}, multipleSelectable = true, onlyLists = false, // Show both filters and lists addCategory = false, diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt index 1fba26d..c6987a4 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt @@ -27,7 +27,6 @@ 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.model.VocabularyCategory import eu.gaudian.translator.model.VocabularyItem import eu.gaudian.translator.utils.findActivity @@ -54,8 +53,6 @@ fun VocabularyReviewScreen( val selectedItems = remember { mutableStateListOf() } val duplicates = remember { mutableStateListOf() } var selectedCategories by remember { mutableStateOf>(emptyList()) } - var newCategoryName by remember { mutableStateOf("") } - var expanded by remember { mutableStateOf(false) } LaunchedEffect(generatedItems) { val duplicateResults = vocabularyViewModel.findDuplicates(generatedItems) @@ -134,21 +131,8 @@ fun VocabularyReviewScreen( style = MaterialTheme.typography.labelLarge, modifier = Modifier.padding(8.dp) ) - CategoryDropdownContent( - state = CategoryDropdownState( - expanded = expanded, - selectedCategories = selectedCategories, - newCategoryName = newCategoryName, - categories = categories, - ), - onExpand = { isExpanded -> expanded = isExpanded }, + CategoryDropdown( onCategorySelected = { selectedCategories = it }, - onNewCategoryNameChange = { newCategoryName = it }, - onAddCategory = { name -> - val newCategory = TagCategory(id = 0, name = name.trim()) - categoryViewModel.createCategory(newCategory) - newCategoryName = "" - }, noneSelectable = false, multipleSelectable = true, onlyLists = true, diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyCardHost.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyCardHost.kt index f632eec..d25fe77 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyCardHost.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyCardHost.kt @@ -238,7 +238,7 @@ fun VocabularyCardHost( listOf(currentVocabularyItem), it.mapNotNull { category -> category?.id } ) - showCategoryDialog = false + //showCategoryDialog = false }, onDismissRequest = { showCategoryDialog = false } ) diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyListScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyListScreen.kt index 6402b29..52cb7af 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyListScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyListScreen.kt @@ -103,7 +103,7 @@ private data class VocabularyFilterState( val searchQuery: String = "", val selectedStage: VocabularyStage? = null, val sortOrder: SortOrder = SortOrder.NEWEST_FIRST, - val categoryId: Int? = null, + val categoryIds: List = emptyList(), val dueTodayOnly: Boolean = false, val selectedLanguageIds: List = emptyList(), val selectedWordClass: String? = null @@ -133,7 +133,7 @@ fun VocabularyListScreen( var filterState by rememberSaveable { mutableStateOf( VocabularyFilterState( - categoryId = categoryId, + categoryIds = categoryId?.let { listOf(it) } ?: emptyList(), dueTodayOnly = showDueTodayOnly == true, selectedStage = stage ) @@ -142,7 +142,7 @@ fun VocabularyListScreen( val isFilterActive by remember(filterState) { derivedStateOf { filterState.selectedStage != null || - (filterState.categoryId != null && filterState.categoryId != 0) || + filterState.categoryIds.isNotEmpty() || filterState.dueTodayOnly || filterState.selectedLanguageIds.isNotEmpty() || !filterState.selectedWordClass.isNullOrBlank() @@ -165,7 +165,7 @@ fun VocabularyListScreen( vocabularyViewModel.filterVocabularyItems( languages = filterState.selectedLanguageIds, query = filterState.searchQuery.takeIf { it.isNotBlank() }, - categoryId = filterState.categoryId, + categoryIds = filterState.categoryIds, stage = filterState.selectedStage, wordClass = filterState.selectedWordClass, dueTodayOnly = filterState.dueTodayOnly, @@ -179,7 +179,7 @@ fun VocabularyListScreen( LaunchedEffect(categoryId, showDueTodayOnly, stage) { filterState = filterState.copy( - categoryId = categoryId, + categoryIds = categoryId?.let { listOf(it) } ?: emptyList(), dueTodayOnly = showDueTodayOnly == true, selectedStage = stage ) @@ -374,7 +374,7 @@ fun VocabularyListScreen( FilterSortBottomSheet( currentFilterState = filterState, onDismiss = { showFilterSheet = false }, - onApplyFilters = { newState -> + onApplyFilters = { newState -> filterState = newState showFilterSheet = false scope.launch { lazyListState.scrollToItem(0) } @@ -382,7 +382,8 @@ fun VocabularyListScreen( languageViewModel = languageViewModel, languagesPresent = allLanguages.filter { it.nameResId in languagesPresent }, hideCategory = categoryId != null && categoryId != 0, - hideStage = stage != null + hideStage = stage != null, + categoryViewModel = categoryViewModel ) } @@ -394,7 +395,7 @@ fun VocabularyListScreen( selectedItems, it.mapNotNull { category -> category?.id } ) - showCategoryDialog = false + //showCategoryDialog = false }, onDismissRequest = { showCategoryDialog = false } ) @@ -807,11 +808,12 @@ private fun FilterSortBottomSheet( onDismiss: () -> Unit, onApplyFilters: (VocabularyFilterState) -> Unit, hideCategory: Boolean = false, - hideStage: Boolean = false + hideStage: Boolean = false, + categoryViewModel: CategoryViewModel ) { var selectedStage by rememberSaveable { mutableStateOf(currentFilterState.selectedStage) } var dueTodayOnly by rememberSaveable { mutableStateOf(currentFilterState.dueTodayOnly) } - var selectedCategoryId by rememberSaveable { mutableStateOf(currentFilterState.categoryId) } + var selectedCategoryIds by rememberSaveable { mutableStateOf(currentFilterState.categoryIds) } var selectedLanguageIds by rememberSaveable { mutableStateOf(currentFilterState.selectedLanguageIds) } var selectedWordClass by rememberSaveable { mutableStateOf(currentFilterState.selectedWordClass) } @@ -840,7 +842,7 @@ private fun FilterSortBottomSheet( TextButton(onClick = { if (!hideStage) selectedStage = null dueTodayOnly = false - if (!hideCategory) selectedCategoryId = null + if (!hideCategory) selectedCategoryIds = emptyList() selectedLanguageIds = emptyList() selectedWordClass = null }) { @@ -853,7 +855,7 @@ private fun FilterSortBottomSheet( currentFilterState.copy( selectedStage = selectedStage, dueTodayOnly = dueTodayOnly, - categoryId = selectedCategoryId, + categoryIds = selectedCategoryIds, selectedLanguageIds = selectedLanguageIds, selectedWordClass = selectedWordClass ) @@ -893,16 +895,18 @@ private fun FilterSortBottomSheet( Text(stringResource(R.string.label_category), style = MaterialTheme.typography.titleMedium) Spacer(Modifier.height(8.dp)) CategoryDropdown( - initialCategoryId = selectedCategoryId, + initialCategoryId = selectedCategoryIds.firstOrNull(), onCategorySelected = { categories -> - selectedCategoryId = categories.firstOrNull()?.id - } + selectedCategoryIds = categories.mapNotNull { it?.id } + }, + multipleSelectable = true, + noneSelectable = false ) Spacer(Modifier.height(16.dp)) } if (!hideStage) { - Text(stringResource(R.string.filter_by_stage), style = MaterialTheme.typography.titleMedium) + Text(stringResource(R.string.label_filter_by_stage), style = MaterialTheme.typography.titleMedium) FlowRow( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) @@ -955,6 +959,7 @@ private fun FilterSortBottomSheet( fun FilterSortBottomSheetPreview() { val activity = LocalContext.current.findActivity() val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) + val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity) FilterSortBottomSheet( currentFilterState = VocabularyFilterState(), languageViewModel = languageViewModel, @@ -962,6 +967,7 @@ fun FilterSortBottomSheetPreview() { onDismiss = {}, onApplyFilters = {}, hideCategory = false, - hideStage = false + hideStage = false, + categoryViewModel = categoryViewModel ) -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt index bf57276..4777761 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt @@ -299,6 +299,7 @@ fun VocabularySortingItem( val activity = LocalContext.current.findActivity() val languageConfigViewModel: LanguageConfigViewModel = hiltViewModel(viewModelStoreOwner = activity) val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) + val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity) var wordFirst by remember { mutableStateOf(item.wordFirst) } var wordSecond by remember { mutableStateOf(item.wordSecond) } var selectedCategories by remember { mutableStateOf>(emptyList()) } @@ -313,6 +314,7 @@ fun VocabularySortingItem( var articlesLangSecond by remember { mutableStateOf(emptySet()) } var showDuplicateDialog by remember { mutableStateOf(false) } + val categories by categoryViewModel.categories.collectAsState(initial = emptyList()) // NEW: Calculate if the item is valid for the "Done" button in faulty mode val isItemNowValid by remember(wordFirst, wordSecond, langFirst, langSecond) { diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index bfcf75b..61dc47c 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -360,7 +360,7 @@ %1$d Tage Fortschritt nach Kategorie Filter anwenden - Nach Stufe filtern + Nach Stufe filtern Kategorie Sprache Alle löschen diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3cafef2..f1e252e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -358,7 +358,7 @@ %1$d dias Progresso por Categoria Aplicar Filtros - Filtrar por Estágio + Filtrar por Estágio Categoria Idioma Limpar Tudo diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7cfdb8c..5c2fd07 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,7 +140,7 @@ Fetching Grammar Details Filter and Sort - Filter by Stage + Filter by Stage Filter by Word Type Find Translations @@ -1039,4 +1039,5 @@ Duplicate Finding the right AI model How translation works + None