Refactor VocabularyListScreen to AllCardsListScreen, introduce NavigationRoutes for centralized route management, and externalize hardcoded strings.

This commit is contained in:
jonasgaudian
2026-02-17 16:26:30 +01:00
parent 02530dafbf
commit db959dab20
12 changed files with 188 additions and 353 deletions

View File

@@ -253,7 +253,7 @@ fun TranslatorApp(
val currentDestination = navBackStackEntry?.destination val currentDestination = navBackStackEntry?.destination
val selectedScreen = Screen.fromDestination(currentDestination) val selectedScreen = Screen.fromDestination(currentDestination)
val isBottomBarHidden = currentDestination?.hierarchy?.any { destination -> @Suppress("HardCodedStringLiteral") val isBottomBarHidden = currentDestination?.hierarchy?.any { destination ->
destination.route in setOf( destination.route in setOf(
Screen.Translation.route, Screen.Translation.route,
Screen.Dictionary.route, Screen.Dictionary.route,
@@ -310,6 +310,7 @@ fun TranslatorApp(
} }
}, },
onPlayClicked = { onPlayClicked = {
@Suppress("HardCodedStringLiteral")
navController.navigate("start_exercise") navController.navigate("start_exercise")
} }
) )

View File

@@ -37,6 +37,7 @@ import eu.gaudian.translator.view.settings.TranslationSettingsScreen
import eu.gaudian.translator.view.settings.settingsGraph import eu.gaudian.translator.view.settings.settingsGraph
import eu.gaudian.translator.view.stats.StatsScreen import eu.gaudian.translator.view.stats.StatsScreen
import eu.gaudian.translator.view.translation.TranslationScreen import eu.gaudian.translator.view.translation.TranslationScreen
import eu.gaudian.translator.view.vocabulary.AllCardsListScreen
import eu.gaudian.translator.view.vocabulary.CategoryDetailScreen import eu.gaudian.translator.view.vocabulary.CategoryDetailScreen
import eu.gaudian.translator.view.vocabulary.CategoryListScreen import eu.gaudian.translator.view.vocabulary.CategoryListScreen
import eu.gaudian.translator.view.vocabulary.LanguageProgressScreen import eu.gaudian.translator.view.vocabulary.LanguageProgressScreen
@@ -47,11 +48,26 @@ import eu.gaudian.translator.view.vocabulary.StageDetailScreen
import eu.gaudian.translator.view.vocabulary.VocabularyCardHost import eu.gaudian.translator.view.vocabulary.VocabularyCardHost
import eu.gaudian.translator.view.vocabulary.VocabularyExerciseHostScreen import eu.gaudian.translator.view.vocabulary.VocabularyExerciseHostScreen
import eu.gaudian.translator.view.vocabulary.VocabularyHeatmapScreen import eu.gaudian.translator.view.vocabulary.VocabularyHeatmapScreen
import eu.gaudian.translator.view.vocabulary.VocabularyListScreen
import eu.gaudian.translator.view.vocabulary.VocabularySortingScreen import eu.gaudian.translator.view.vocabulary.VocabularySortingScreen
private const val TRANSITION_DURATION = 300 private const val TRANSITION_DURATION = 300
object NavigationRoutes {
const val NEW_WORD = "new_word"
const val NEW_WORD_REVIEW = "new_word_review"
const val START_EXERCISE = "start_exercise"
const val CATEGORY_DETAIL = "category_detail"
const val CATEGORY_LIST = "category_list_screen"
const val VOCABULARY_DETAIL = "vocabulary_detail"
const val STATS_VOCABULARY_HEATMAP = "stats/vocabulary_heatmap"
const val STATS_LANGUAGE_PROGRESS = "stats/language_progress"
const val STATS_CATEGORY_DETAIL = "stats/category_detail"
const val STATS_CATEGORY_LIST = "stats/category_list_screen"
const val STATS_VOCABULARY_SORTING = "stats/vocabulary_sorting"
const val STATS_NO_GRAMMAR_ITEMS = "stats/no_grammar_items"
const val STATS_VOCABULARY_LIST = "stats/vocabulary_list"
}
@Composable @Composable
fun AppNavHost( fun AppNavHost(
navController: NavHostController, navController: NavHostController,
@@ -130,15 +146,15 @@ fun AppNavHost(
HomeScreen(navController = navController) HomeScreen(navController = navController)
} }
composable("new_word") { composable(NavigationRoutes.NEW_WORD) {
NewWordScreen(navController = navController) NewWordScreen(navController = navController)
} }
composable("new_word_review") { composable(NavigationRoutes.NEW_WORD_REVIEW) {
NewWordReviewScreen(navController = navController) NewWordReviewScreen(navController = navController)
} }
composable("start_exercise") { composable(NavigationRoutes.START_EXERCISE) {
StartExerciseScreen(navController = navController) StartExerciseScreen(navController = navController)
} }
@@ -203,7 +219,7 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) {
composable("vocabulary_list/{showDueTodayOnly}/{categoryId}") { backStackEntry -> composable("vocabulary_list/{showDueTodayOnly}/{categoryId}") { backStackEntry ->
val showDueTodayOnly = backStackEntry.arguments?.getString("showDueTodayOnly")?.toBoolean() ?: false val showDueTodayOnly = backStackEntry.arguments?.getString("showDueTodayOnly")?.toBoolean() ?: false
val categoryId = backStackEntry.arguments?.getString("categoryId")?.toIntOrNull() val categoryId = backStackEntry.arguments?.getString("categoryId")?.toIntOrNull()
VocabularyListScreen( AllCardsListScreen(
navController = navController, navController = navController,
showDueTodayOnly = showDueTodayOnly, showDueTodayOnly = showDueTodayOnly,
categoryId = categoryId, categoryId = categoryId,
@@ -220,7 +236,7 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) {
) )
} }
composable("vocabulary_heatmap") { composable(NavigationRoutes.STATS_VOCABULARY_HEATMAP) {
VocabularyHeatmapScreen( VocabularyHeatmapScreen(
navController = navController, navController = navController,
) )
@@ -232,7 +248,7 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) {
if (it.equals("null", ignoreCase = true)) null else VocabularyStage.valueOf(it) if (it.equals("null", ignoreCase = true)) null else VocabularyStage.valueOf(it)
} }
VocabularyListScreen( AllCardsListScreen(
navController = navController, navController = navController,
showDueTodayOnly = showDueTodayOnly, showDueTodayOnly = showDueTodayOnly,
stage = stage, stage = stage,
@@ -373,7 +389,7 @@ fun NavGraphBuilder.statsGraph(
composable("stats/vocabulary_list/{showDueTodayOnly}/{categoryId}") { backStackEntry -> composable("stats/vocabulary_list/{showDueTodayOnly}/{categoryId}") { backStackEntry ->
val showDueTodayOnly = backStackEntry.arguments?.getString("showDueTodayOnly")?.toBoolean() ?: false val showDueTodayOnly = backStackEntry.arguments?.getString("showDueTodayOnly")?.toBoolean() ?: false
val categoryId = backStackEntry.arguments?.getString("categoryId")?.toIntOrNull() val categoryId = backStackEntry.arguments?.getString("categoryId")?.toIntOrNull()
VocabularyListScreen( AllCardsListScreen(
navController = navController, navController = navController,
showDueTodayOnly = showDueTodayOnly, showDueTodayOnly = showDueTodayOnly,
categoryId = categoryId, categoryId = categoryId,
@@ -384,12 +400,12 @@ fun NavGraphBuilder.statsGraph(
enableNavigationButtons = true enableNavigationButtons = true
) )
} }
composable("stats/language_progress") { composable(NavigationRoutes.STATS_LANGUAGE_PROGRESS) {
LanguageProgressScreen( LanguageProgressScreen(
navController = navController navController = navController
) )
} }
composable("stats/vocabulary_heatmap") { composable(NavigationRoutes.STATS_VOCABULARY_HEATMAP) {
VocabularyHeatmapScreen( VocabularyHeatmapScreen(
navController = navController, navController = navController,
) )
@@ -401,7 +417,7 @@ fun NavGraphBuilder.statsGraph(
if (it.equals("null", ignoreCase = true)) null else VocabularyStage.valueOf(it) if (it.equals("null", ignoreCase = true)) null else VocabularyStage.valueOf(it)
} }
VocabularyListScreen( AllCardsListScreen(
navController = navController, navController = navController,
showDueTodayOnly = showDueTodayOnly, showDueTodayOnly = showDueTodayOnly,
stage = stage, stage = stage,

View File

@@ -43,9 +43,8 @@ import eu.gaudian.translator.view.hints.LocalShowHints
@Composable @Composable
fun AppTopAppBar( fun AppTopAppBar(
title: String,
additionalContent: @Composable () -> Unit = {},
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String,
onNavigateBack: (() -> Unit)? = null, onNavigateBack: (() -> Unit)? = null,
navigationIcon: @Composable (() -> Unit)? = null, navigationIcon: @Composable (() -> Unit)? = null,
actions: @Composable RowScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {},

View File

@@ -15,7 +15,7 @@ import androidx.navigation.NavHostController
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppButton import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.vocabulary.VocabularyListScreen import eu.gaudian.translator.view.vocabulary.AllCardsListScreen
@Composable @Composable
fun ExerciseVocabularyScreen( fun ExerciseVocabularyScreen(
@@ -41,7 +41,7 @@ fun ExerciseVocabularyScreen(
) { paddingValues -> ) { paddingValues ->
Box(modifier = Modifier.padding(paddingValues)) { Box(modifier = Modifier.padding(paddingValues)) {
VocabularyListScreen( AllCardsListScreen(
navController = navController as NavHostController?, navController = navController as NavHostController?,
onNavigateToItem = { item -> onNavigateToItem = { item ->
// Navigate to the detail screen for a specific vocabulary item // Navigate to the detail screen for a specific vocabulary item

View File

@@ -1,5 +1,6 @@
package eu.gaudian.translator.view.home package eu.gaudian.translator.view.home
import android.content.Context
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -37,12 +38,14 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.NavigationRoutes
import eu.gaudian.translator.view.composable.AppCard import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.Screen import eu.gaudian.translator.view.composable.Screen
import eu.gaudian.translator.view.settings.SettingsRoutes import eu.gaudian.translator.view.settings.SettingsRoutes
@@ -78,33 +81,36 @@ fun HomeScreen(
verticalArrangement = Arrangement.spacedBy(15.dp) verticalArrangement = Arrangement.spacedBy(15.dp)
) { ) {
item { Spacer(modifier = Modifier.height(16.dp)) } item { Spacer(modifier = Modifier.height(16.dp)) }
item { TopProfileSection(navController = navController) } item { TopProfileSection(
navController = navController,
context = LocalContext.current
) }
item { item {
StreakAndGoalSection( StreakAndGoalSection(
streak = streak, streak = streak,
progress = progress, progress = progress,
progressTitle = "$todayCompletedCount / $dailyGoal", progressTitle = "$todayCompletedCount / $dailyGoal",
onGoalClick = { navController.navigate(SettingsRoutes.VOCABULARY_OPTIONS) }, onGoalClick = { navController.navigate(SettingsRoutes.VOCABULARY_OPTIONS) },
onStreakClick = { navController.navigate("stats/vocabulary_heatmap") } onStreakClick = { navController.navigate(NavigationRoutes.STATS_VOCABULARY_HEATMAP) }
) )
} }
item { item {
//TODO replace with actual implementation
@Suppress("HardCodedStringLiteral")
ActionCard( ActionCard(
title = "Daily Review", title = "Daily Review",
subtitle = "42 words need attention", subtitle = "42 words need attention",
icon = Icons.Default.Psychology, icon = Icons.Default.Psychology,
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary contentColor = MaterialTheme.colorScheme.onPrimary
) )
} }
item { item {
ActionCard( ActionCard(
title = "New Words", title = stringResource(R.string.label_new_words),
subtitle = "Expand your vocabulary", subtitle = stringResource(R.string.desc_expand_your_vocabulary),
icon = Icons.Default.AddCircle, icon = Icons.Default.AddCircle,
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant, contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
onClick = { navController.navigate("new_word") } onClick = { navController.navigate(NavigationRoutes.NEW_WORD) }
) )
} }
item { WeeklyProgressSection(navController = navController) } item { WeeklyProgressSection(navController = navController) }
@@ -117,8 +123,8 @@ fun HomeScreen(
} }
@Composable @Composable
fun TopProfileSection(navController: NavHostController) { fun TopProfileSection(navController: NavHostController, context: Context) {
val context = LocalContext.current
val motivationalPhrases = remember { val motivationalPhrases = remember {
context.resources.getStringArray(R.array.motivational_phrases) context.resources.getStringArray(R.array.motivational_phrases)
} }
@@ -126,7 +132,9 @@ fun TopProfileSection(navController: NavHostController) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(8.dp) modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) { ) {
Column(modifier = Modifier.weight(1f)) { Column(modifier = Modifier.weight(1f)) {
@@ -146,7 +154,7 @@ fun TopProfileSection(navController: NavHostController) {
) { ) {
Icon( Icon(
imageVector = Icons.Default.Settings, imageVector = Icons.Default.Settings,
contentDescription = "Settings", contentDescription = stringResource(R.string.label_settings),
tint = MaterialTheme.colorScheme.onSurfaceVariant tint = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -169,8 +177,8 @@ fun StreakAndGoalSection(
StatCard( StatCard(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
icon = Icons.Default.LocalFireDepartment, icon = Icons.Default.LocalFireDepartment,
title = "$streak Days", title = stringResource(R.string.label_2d_days, streak),
subtitle = "CURRENT STREAK", subtitle = stringResource(R.string.label_current_streak).uppercase(),
onClick = onStreakClick onClick = onStreakClick
) )
// Goal Card // Goal Card
@@ -178,7 +186,7 @@ fun StreakAndGoalSection(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
progress = progress, progress = progress,
title = progressTitle, title = progressTitle,
subtitle = "DAILY GOAL", subtitle = stringResource(R.string.label_daily_goal).uppercase(),
onClick = onGoalClick onClick = onGoalClick
) )
} }
@@ -293,7 +301,6 @@ fun ActionCard(
title: String, title: String,
subtitle: String, subtitle: String,
icon: ImageVector, icon: ImageVector,
containerColor: Color,
contentColor: Color, contentColor: Color,
onClick: (() -> Unit)? = null onClick: (() -> Unit)? = null
) { ) {
@@ -314,7 +321,7 @@ fun ActionCard(
} }
Icon( Icon(
imageVector = Icons.Default.ChevronRight, imageVector = Icons.Default.ChevronRight,
contentDescription = "Go", contentDescription = stringResource(R.string.cd_go),
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
} }
@@ -351,9 +358,9 @@ fun WeeklyProgressSection(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text(text = "Weekly Progress", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) Text(text = stringResource(R.string.label_weekly_progress), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
TextButton(onClick = { navController.navigate("stats/vocabulary_heatmap") }) { TextButton(onClick = { navController.navigate(NavigationRoutes.STATS_VOCABULARY_HEATMAP) }) {
Text("See History") Text(stringResource(R.string.label_see_history))
} }
} }
@@ -361,7 +368,7 @@ fun WeeklyProgressSection(
AppCard( AppCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onClick = { navController.navigate("stats/vocabulary_heatmap") } onClick = { navController.navigate(NavigationRoutes.STATS_VOCABULARY_HEATMAP) }
) { ) {
if (weeklyActivityStats.isEmpty()) { if (weeklyActivityStats.isEmpty()) {
Column( Column(
@@ -372,7 +379,7 @@ fun WeeklyProgressSection(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text( Text(
text = "No activity data available", text = stringResource(R.string.text_desc_no_activity_data_available),
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
@@ -403,7 +410,7 @@ fun BottomStatsSection(
onClick = { navController.navigate(Screen.Library.route) } onClick = { navController.navigate(Screen.Library.route) }
) { ) {
Column(modifier = Modifier.padding(20.dp)) { Column(modifier = Modifier.padding(20.dp)) {
Text(text = "TOTAL WORDS", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)) Text(text = stringResource(R.string.label_total_words).uppercase(), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f))
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text(text = totalWords.toString(), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) Text(text = totalWords.toString(), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -413,7 +420,7 @@ fun BottomStatsSection(
// Learned // Learned
AppCard( AppCard(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
onClick = { navController.navigate("stats/language_progress") } onClick = { navController.navigate(NavigationRoutes.STATS_LANGUAGE_PROGRESS) }
) { ) {
Column(modifier = Modifier.padding(20.dp)) { Column(modifier = Modifier.padding(20.dp)) {
Text(text = "LEARNED", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)) Text(text = "LEARNED", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f))

View File

@@ -1,4 +1,4 @@
@file:Suppress("HardCodedStringLiteral") @file:Suppress("AssignedValueIsNeverRead")
package eu.gaudian.translator.view.library package eu.gaudian.translator.view.library
@@ -44,6 +44,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -61,8 +62,10 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.R.string.text_add_new_word_to_list
import eu.gaudian.translator.model.VocabularyStage import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.NavigationRoutes
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppSwitch import eu.gaudian.translator.view.composable.AppSwitch
import eu.gaudian.translator.view.composable.BottomSheetMenuItem import eu.gaudian.translator.view.composable.BottomSheetMenuItem
@@ -70,7 +73,6 @@ import eu.gaudian.translator.view.composable.MultipleLanguageDropdown
import eu.gaudian.translator.view.dialogs.AddCategoryDialog import eu.gaudian.translator.view.dialogs.AddCategoryDialog
import eu.gaudian.translator.view.dialogs.CategorySelectionDialog import eu.gaudian.translator.view.dialogs.CategorySelectionDialog
import eu.gaudian.translator.view.dialogs.StageSelectionDialog import eu.gaudian.translator.view.dialogs.StageSelectionDialog
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.LanguageConfigViewModel import eu.gaudian.translator.viewmodel.LanguageConfigViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel import eu.gaudian.translator.viewmodel.LanguageViewModel
import eu.gaudian.translator.viewmodel.VocabularyViewModel import eu.gaudian.translator.viewmodel.VocabularyViewModel
@@ -99,8 +101,6 @@ fun LibraryScreen(
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity) val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity)
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
val context = LocalContext.current
var filterState by rememberSaveable { mutableStateOf(LibraryFilterState()) } var filterState by rememberSaveable { mutableStateOf(LibraryFilterState()) }
var showFilterSheet by remember { mutableStateOf(false) } var showFilterSheet by remember { mutableStateOf(false) }
@@ -135,8 +135,8 @@ fun LibraryScreen(
val vocabularyItems by vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList()) val vocabularyItems by vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList())
var isHeaderVisible by remember { mutableStateOf(true) } var isHeaderVisible by remember { mutableStateOf(true) }
var previousIndex by remember { mutableStateOf(0) } var previousIndex by remember { mutableIntStateOf(0) }
var previousScrollOffset by remember { mutableStateOf(0) } var previousScrollOffset by remember { mutableIntStateOf(0) }
// Set navigation context when vocabulary items are loaded // Set navigation context when vocabulary items are loaded
LaunchedEffect(vocabularyItems) { LaunchedEffect(vocabularyItems) {
@@ -229,10 +229,11 @@ fun LibraryScreen(
CategoriesView( CategoriesView(
categories = categories, categories = categories,
onCategoryClick = { category -> onCategoryClick = { category ->
navController.navigate("category_detail/${category.id}") @Suppress("HardCodedStringLiteral")
navController.navigate("${NavigationRoutes.CATEGORY_DETAIL}/${category.id}")
}, },
onExploreMoreClick = { onExploreMoreClick = {
navController.navigate("category_list_screen") navController.navigate(NavigationRoutes.CATEGORY_LIST)
} }
) )
}, },
@@ -251,7 +252,8 @@ fun LibraryScreen(
} }
} else { } else {
vocabularyViewModel.setNavigationContext(vocabularyItems, item.id) vocabularyViewModel.setNavigationContext(vocabularyItems, item.id)
navController.navigate("vocabulary_detail/${item.id}") @Suppress("HardCodedStringLiteral")
navController.navigate("${NavigationRoutes.VOCABULARY_DETAIL}/${item.id}")
} }
}, },
onItemLongClick = { item -> onItemLongClick = { item ->
@@ -287,7 +289,7 @@ fun LibraryScreen(
modifier = Modifier.size(50.dp), modifier = Modifier.size(50.dp),
containerColor = MaterialTheme.colorScheme.surfaceVariant containerColor = MaterialTheme.colorScheme.surfaceVariant
) { ) {
Icon(AppIcons.ArrowCircleUp, contentDescription = "Scroll to top") Icon(AppIcons.ArrowCircleUp, contentDescription = stringResource(R.string.cd_scroll_to_top))
} }
} }
} }
@@ -344,17 +346,17 @@ fun LibraryScreen(
BottomSheetMenuItem( BottomSheetMenuItem(
icon = Icons.Rounded.Add, // Or any custom vector icon you prefer icon = Icons.Rounded.Add, // Or any custom vector icon you prefer
title = stringResource(R.string.label_add_vocabulary), title = stringResource(R.string.label_add_vocabulary),
subtitle = "Füge ein neues Wort zu deiner Liste hinzu", // Suggest adding this to strings.xml subtitle = stringResource(text_add_new_word_to_list), // Suggest adding this to strings.xml
onClick = { onClick = {
showAddMenu = false showAddMenu = false
navController.navigate("new_word") navController.navigate(NavigationRoutes.NEW_WORD)
} }
) )
BottomSheetMenuItem( BottomSheetMenuItem(
icon = Icons.Rounded.Folder, icon = Icons.Rounded.Folder,
title = stringResource(R.string.label_add_category), title = stringResource(R.string.label_add_category),
subtitle = "Organisiere deine Vokabeln in Gruppen", // Suggest adding this to strings.xml subtitle = stringResource(R.string.text_desc_organize_vocabulary_groups), // Suggest adding this to strings.xml
onClick = { onClick = {
showAddMenu = false showAddMenu = false
showAddCategoryDialog = true showAddCategoryDialog = true
@@ -418,7 +420,7 @@ fun FilterBottomSheetContent(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = "Filter Cards", text = stringResource(R.string.label_filter_cards),
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
@@ -430,7 +432,7 @@ fun FilterBottomSheetContent(
sortOrder = SortOrder.NEWEST_FIRST sortOrder = SortOrder.NEWEST_FIRST
onResetClick() onResetClick()
}) { }) {
Text("Reset") Text(stringResource(R.string.label_reset))
} }
} }
@@ -446,7 +448,7 @@ fun FilterBottomSheetContent(
// Sort Order // Sort Order
Column { Column {
Text( Text(
text = "SORT BY", text = stringResource(R.string.label_sort_by).uppercase(),
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
letterSpacing = 1.sp, letterSpacing = 1.sp,
@@ -589,7 +591,7 @@ fun FilterBottomSheetContent(
shape = RoundedCornerShape(28.dp) shape = RoundedCornerShape(28.dp)
) { ) {
Text( Text(
text = "Apply Filters", text = stringResource(R.string.label_apply_filters),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )

View File

@@ -1,4 +1,4 @@
@file:Suppress("HardCodedStringLiteral", "AssignedValueIsNeverRead", "UnusedMaterial3ScaffoldPaddingParameter") @file:Suppress("AssignedValueIsNeverRead")
package eu.gaudian.translator.view.stats package eu.gaudian.translator.view.stats
@@ -56,8 +56,8 @@ import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.model.VocabularyStage import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.model.WidgetType import eu.gaudian.translator.model.WidgetType
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.NavigationRoutes
import eu.gaudian.translator.view.composable.AppCard import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedCard import eu.gaudian.translator.view.composable.AppOutlinedCard
@@ -82,14 +82,11 @@ import kotlinx.coroutines.launch
@SuppressLint("FrequentlyChangingValue") @SuppressLint("FrequentlyChangingValue")
@Composable @Composable
fun StatsScreen( fun StatsScreen(
modifier: Modifier = Modifier,
navController: NavHostController, navController: NavHostController,
onShowCustomExerciseDialog: () -> Unit = {},
startDailyExercise: (Boolean) -> Unit = {},
onNavigateToCategoryDetail: ((Int) -> Unit)? = null, onNavigateToCategoryDetail: ((Int) -> Unit)? = null,
onNavigateToCategoryList: (() -> Unit)? = null, onNavigateToCategoryList: (() -> Unit)? = null,
onShowWordPairExerciseDialog: () -> Unit = {},
onScroll: (Boolean) -> Unit = {}, onScroll: (Boolean) -> Unit = {},
modifier: Modifier = Modifier
) { ) {
val activity = LocalContext.current.findActivity() val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
@@ -128,10 +125,11 @@ fun StatsScreen(
} }
val handleNavigateToCategoryDetail = onNavigateToCategoryDetail ?: { categoryId -> val handleNavigateToCategoryDetail = onNavigateToCategoryDetail ?: { categoryId ->
navController.navigate("stats/category_detail/$categoryId") @Suppress("HardCodedStringLiteral")
navController.navigate("${NavigationRoutes.STATS_CATEGORY_DETAIL}/$categoryId")
} }
val handleNavigateToCategoryList = onNavigateToCategoryList ?: { val handleNavigateToCategoryList = onNavigateToCategoryList ?: {
navController.navigate("stats/category_list_screen") navController.navigate(NavigationRoutes.STATS_CATEGORY_LIST)
} }
AppOutlinedCard(modifier = modifier) { AppOutlinedCard(modifier = modifier) {
@@ -270,11 +268,8 @@ fun StatsScreen(
navController = navController, navController = navController,
vocabularyViewModel = vocabularyViewModel, vocabularyViewModel = vocabularyViewModel,
progressViewModel = progressViewModel, progressViewModel = progressViewModel,
onShowCustomExerciseDialog = onShowCustomExerciseDialog,
startDailyExercise = startDailyExercise,
onNavigateToCategoryDetail = handleNavigateToCategoryDetail, onNavigateToCategoryDetail = handleNavigateToCategoryDetail,
onNavigateToCategoryList = handleNavigateToCategoryList, onNavigateToCategoryList = handleNavigateToCategoryList,
onShowWordPairExerciseDialog = onShowWordPairExerciseDialog,
onMissingLanguage = { missingId -> onMissingLanguage = { missingId ->
selectedMissingLanguageId = missingId selectedMissingLanguageId = missingId
showMissingLanguageDialog = true showMissingLanguageDialog = true
@@ -524,17 +519,15 @@ class DragDropState(
// Remainder of your existing components // Remainder of your existing components
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
@Suppress("HardCodedStringLiteral")
@Composable @Composable
private fun LazyWidget( private fun LazyWidget(
widgetType: WidgetType, widgetType: WidgetType,
navController: NavController, navController: NavController,
vocabularyViewModel: VocabularyViewModel, vocabularyViewModel: VocabularyViewModel,
progressViewModel: ProgressViewModel, progressViewModel: ProgressViewModel,
onShowCustomExerciseDialog: () -> Unit,
startDailyExercise: (Boolean) -> Unit,
onNavigateToCategoryDetail: (Int) -> Unit, onNavigateToCategoryDetail: (Int) -> Unit,
onNavigateToCategoryList: () -> Unit, onNavigateToCategoryList: () -> Unit,
onShowWordPairExerciseDialog: () -> Unit,
onMissingLanguage: (Int) -> Unit onMissingLanguage: (Int) -> Unit
) { ) {
when (widgetType) { when (widgetType) {
@@ -542,10 +535,10 @@ private fun LazyWidget(
WidgetType.Status -> LazyStatusWidget( WidgetType.Status -> LazyStatusWidget(
vocabularyViewModel = vocabularyViewModel, vocabularyViewModel = vocabularyViewModel,
onNavigateToNew = { navController.navigate("stats/vocabulary_sorting?mode=NEW") }, onNavigateToNew = { navController.navigate("${NavigationRoutes.STATS_VOCABULARY_SORTING}?mode=NEW") },
onNavigateToDuplicates = { navController.navigate("stats/vocabulary_sorting?mode=DUPLICATES") }, onNavigateToDuplicates = { navController.navigate("${NavigationRoutes.STATS_VOCABULARY_SORTING}?mode=DUPLICATES") },
onNavigateToFaulty = { navController.navigate("stats/vocabulary_sorting?mode=FAULTY") }, onNavigateToFaulty = { navController.navigate("${NavigationRoutes.STATS_VOCABULARY_SORTING}?mode=FAULTY") },
onNavigateToNoGrammar = { navController.navigate("stats/no_grammar_items") }, onNavigateToNoGrammar = { navController.navigate(NavigationRoutes.STATS_NO_GRAMMAR_ITEMS) },
onNavigateToMissingLanguage = onMissingLanguage onNavigateToMissingLanguage = onMissingLanguage
) )
@@ -557,7 +550,7 @@ private fun LazyWidget(
lastSevenDays = progressViewModel.lastSevenDays.collectAsState().value, lastSevenDays = progressViewModel.lastSevenDays.collectAsState().value,
dueTodayCount = progressViewModel.dueTodayCount.collectAsState().value, dueTodayCount = progressViewModel.dueTodayCount.collectAsState().value,
wordsCompleted = progressViewModel.totalWordsCompleted.collectAsState().value, wordsCompleted = progressViewModel.totalWordsCompleted.collectAsState().value,
onStatisticsClicked = { navController.navigate("stats/vocabulary_heatmap") } onStatisticsClicked = { navController.navigate(NavigationRoutes.STATS_VOCABULARY_HEATMAP) }
) )
WidgetType.WeeklyActivityChart -> WeeklyActivityChartWidget( WidgetType.WeeklyActivityChart -> WeeklyActivityChartWidget(
@@ -566,13 +559,19 @@ private fun LazyWidget(
WidgetType.AllVocabulary -> AllVocabularyWidget( WidgetType.AllVocabulary -> AllVocabularyWidget(
vocabularyViewModel = vocabularyViewModel, vocabularyViewModel = vocabularyViewModel,
onOpenAllVocabulary = { navController.navigate("stats/vocabulary_list/false/null") }, onOpenAllVocabulary = { navController.navigate("${NavigationRoutes.STATS_VOCABULARY_LIST}/false/null") },
onStageClicked = { stage -> navController.navigate("stats/vocabulary_list/false/$stage") } onStageClicked = { stage ->
@Suppress("HardCodedStringLiteral")
navController.navigate("${NavigationRoutes.STATS_VOCABULARY_LIST}/false/$stage")
}
) )
WidgetType.DueToday -> DueTodayWidget( WidgetType.DueToday -> DueTodayWidget(
vocabularyViewModel = vocabularyViewModel, vocabularyViewModel = vocabularyViewModel,
onStageClicked = { stage -> navController.navigate("stats/vocabulary_list/false/$stage") } onStageClicked = { stage ->
@Suppress("HardCodedStringLiteral")
navController.navigate("${NavigationRoutes.STATS_VOCABULARY_LIST}/false/$stage")
}
) )
WidgetType.CategoryProgress -> CategoryProgressWidget( WidgetType.CategoryProgress -> CategoryProgressWidget(
@@ -586,7 +585,7 @@ private fun LazyWidget(
totalWords = vocabularyViewModel.vocabularyItems.collectAsState().value.size, totalWords = vocabularyViewModel.vocabularyItems.collectAsState().value.size,
learnedWords = vocabularyViewModel.stageStats.collectAsState().value learnedWords = vocabularyViewModel.stageStats.collectAsState().value
.firstOrNull { it.stage == VocabularyStage.LEARNED }?.itemCount ?: 0, .firstOrNull { it.stage == VocabularyStage.LEARNED }?.itemCount ?: 0,
onNavigateToProgress = { navController.navigate("stats/language_progress") } onNavigateToProgress = { navController.navigate(NavigationRoutes.STATS_LANGUAGE_PROGRESS) }
) )
} }
@@ -649,11 +648,8 @@ fun StatsScreenPreview() {
val navController = rememberNavController() val navController = rememberNavController()
StatsScreen( StatsScreen(
navController = navController, navController = navController,
onShowCustomExerciseDialog = {},
onNavigateToCategoryDetail = {}, onNavigateToCategoryDetail = {},
startDailyExercise = {},
onNavigateToCategoryList = {}, onNavigateToCategoryList = {},
onShowWordPairExerciseDialog = {},
) )
} }
@@ -675,6 +671,7 @@ fun WidgetContainerPreview() {
.padding(16.dp), .padding(16.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
@Suppress("HardCodedStringLiteral")
Text("Preview Content") Text("Preview Content")
} }
} }

View File

@@ -179,7 +179,7 @@ fun CategoryDetailScreen(
} }
) { paddingValues -> ) { paddingValues ->
Column(modifier = Modifier.padding(paddingValues)) { Column(modifier = Modifier.padding(paddingValues)) {
VocabularyListScreen( AllCardsListScreen(
categoryId = categoryId, categoryId = categoryId,
showDueTodayOnly = false, showDueTodayOnly = false,
onNavigateToItem = onNavigateToItem, onNavigateToItem = onNavigateToItem,
@@ -324,8 +324,8 @@ fun CategoryHeaderCardWithProgressPreview() {
MaterialTheme { MaterialTheme {
CategoryHeaderCard( CategoryHeaderCard(
subtitle = "Travel Vocabulary", subtitle = "Travel Vocabulary",
categoryProgress = eu.gaudian.translator.viewmodel.CategoryProgress( categoryProgress = CategoryProgress(
vocabularyCategory = eu.gaudian.translator.model.TagCategory( vocabularyCategory = TagCategory(
1, 1,
"Travel" "Travel"
), ),

View File

@@ -80,8 +80,8 @@ fun NoGrammarItemsScreen(
} }
} }
} else { } else {
// Use the generic VocabularyListScreen to display the items // Use the generic AllCardsListScreen to display the items
VocabularyListScreen( AllCardsListScreen(
itemsToShow = itemsWithoutGrammar, itemsToShow = itemsWithoutGrammar,
onNavigateToItem = { item -> onNavigateToItem = { item ->
@Suppress("HardCodedStringLiteral") @Suppress("HardCodedStringLiteral")

View File

@@ -49,7 +49,7 @@ fun StageDetailScreen(
onStageTapped = {}, onStageTapped = {},
) )
VocabularyListScreen( AllCardsListScreen(
categoryId = null, categoryId = null,
showDueTodayOnly = true, showDueTodayOnly = true,
stage = stage, stage = stage,

View File

@@ -5,19 +5,13 @@ package eu.gaudian.translator.view.vocabulary
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -25,14 +19,10 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FabPosition import androidx.compose.material3.FabPosition
@@ -43,7 +33,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@@ -59,20 +48,15 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language import eu.gaudian.translator.model.Language
import eu.gaudian.translator.model.VocabularyItem import eu.gaudian.translator.model.VocabularyItem
@@ -84,10 +68,10 @@ import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppSwitch import eu.gaudian.translator.view.composable.AppSwitch
import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.composable.MultipleLanguageDropdown import eu.gaudian.translator.view.composable.MultipleLanguageDropdown
import eu.gaudian.translator.view.composable.insertBreakOpportunities
import eu.gaudian.translator.view.dialogs.CategoryDropdown import eu.gaudian.translator.view.dialogs.CategoryDropdown
import eu.gaudian.translator.view.dialogs.CategorySelectionDialog import eu.gaudian.translator.view.dialogs.CategorySelectionDialog
import eu.gaudian.translator.view.dialogs.StageSelectionDialog import eu.gaudian.translator.view.dialogs.StageSelectionDialog
import eu.gaudian.translator.view.library.AllCardsView
import eu.gaudian.translator.viewmodel.CategoryViewModel import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.LanguageConfigViewModel import eu.gaudian.translator.viewmodel.LanguageConfigViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel import eu.gaudian.translator.viewmodel.LanguageViewModel
@@ -110,7 +94,7 @@ private data class VocabularyFilterState(
) : Parcelable ) : Parcelable
@Composable @Composable
fun VocabularyListScreen( fun AllCardsListScreen(
categoryId: Int? = null, categoryId: Int? = null,
showDueTodayOnly: Boolean? = null, showDueTodayOnly: Boolean? = null,
stage: VocabularyStage? = null, stage: VocabularyStage? = null,
@@ -245,10 +229,6 @@ fun VocabularyListScreen(
) )
"search" -> SearchTopAppBar( "search" -> SearchTopAppBar(
searchQuery = filterState.searchQuery,
onQueryChanged = { newQuery ->
filterState = filterState.copy(searchQuery = newQuery)
},
onCloseSearch = { onCloseSearch = {
isSearchActive = false isSearchActive = false
filterState = filterState.copy(searchQuery = "") filterState = filterState.copy(searchQuery = "")
@@ -295,50 +275,16 @@ fun VocabularyListScreen(
floatingActionButtonPosition = FabPosition.Center floatingActionButtonPosition = FabPosition.Center
) { paddingValues -> ) { paddingValues ->
Column(modifier = Modifier.padding(paddingValues).padding(bottom = 0.dp)) { Column(modifier = Modifier.padding(paddingValues).padding(bottom = 0.dp)) {
if (vocabularyItems.isEmpty()) { AllCardsView(
Column( vocabularyItems = vocabularyItems,
modifier = Modifier allLanguages = allLanguages,
.fillMaxSize() selection = selection,
.padding(16.dp), listState = lazyListState,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
modifier = Modifier.size(200.dp),
painter = painterResource(id = R.drawable.ic_nothing_found),
contentDescription = stringResource(id = R.string.no_vocabulary_items_found_perhaps_try_changing_the_filters)
)
Spacer(modifier = Modifier.size(16.dp))
Box(modifier = Modifier
.fillMaxSize()
.padding(8.dp), contentAlignment = Alignment.Center) {
Text(
text = stringResource(R.string.no_vocabulary_items_found_perhaps_try_changing_the_filters),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
)
}
}
} else {
LazyColumn(
state = lazyListState,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), onItemClick = { item ->
contentPadding = PaddingValues(vertical = 0.dp)
) {
items(
items = vocabularyItems,
key = { it.id }
) { item ->
val isSelected = selection.contains(item.id.toLong()) val isSelected = selection.contains(item.id.toLong())
VocabularyListItem(
item = item,
allLanguages = allLanguages,
isSelected = isSelected,
onItemClick = {
if (isInSelectionMode) { if (isInSelectionMode) {
selection = if (isSelected) { selection = if (isSelected) {
selection - item.id.toLong() selection - item.id.toLong()
@@ -354,20 +300,16 @@ fun VocabularyListScreen(
} }
} }
}, },
onItemLongClick = { onItemLongClick = { item ->
if (!isInSelectionMode) { if (!isInSelectionMode) {
selection = setOf(item.id.toLong()) selection = setOf(item.id.toLong())
} }
}, },
onDeleteClick = { onDeleteClick = { item ->
vocabularyViewModel.deleteData(VocabularyViewModel.DeleteType.VOCABULARY_ITEM, item = item) vocabularyViewModel.deleteData(VocabularyViewModel.DeleteType.VOCABULARY_ITEM, item = item)
}, }
modifier = Modifier.animateItem()
) )
} }
}
}
}
if (showFilterSheet) { if (showFilterSheet) {
@@ -382,8 +324,7 @@ fun VocabularyListScreen(
languageViewModel = languageViewModel, languageViewModel = languageViewModel,
languagesPresent = allLanguages.filter { it.nameResId in languagesPresent }, languagesPresent = allLanguages.filter { it.nameResId in languagesPresent },
hideCategory = categoryId != null && categoryId != 0, hideCategory = categoryId != null && categoryId != 0,
hideStage = stage != null, hideStage = stage != null
categoryViewModel = categoryViewModel
) )
} }
@@ -417,21 +358,34 @@ fun VocabularyListScreen(
} }
} }
@ThemePreviews @Deprecated("Use AllCardsListScreen which renders AllCardsView")
@Composable @Composable
fun VocabularyListScreenPreview() { fun VocabularyListScreen(
val navController = rememberNavController() categoryId: Int? = null,
VocabularyListScreen( showDueTodayOnly: Boolean? = null,
categoryId = 1, stage: VocabularyStage? = null,
showDueTodayOnly = false, onNavigateToItem: (VocabularyItem) -> Unit?,
stage = VocabularyStage.NEW, onNavigateBack: (() -> Unit)? = null,
onNavigateToItem = {}, navController: NavHostController? = null,
onNavigateBack = {}, itemsToShow: List<VocabularyItem> = emptyList(),
navController = navController isRemoveFromCategoryEnabled: Boolean = false,
showTopBar: Boolean = true,
enableNavigationButtons: Boolean = false
) {
AllCardsListScreen(
categoryId = categoryId,
showDueTodayOnly = showDueTodayOnly,
stage = stage,
onNavigateToItem = onNavigateToItem,
onNavigateBack = onNavigateBack,
navController = navController,
itemsToShow = itemsToShow,
isRemoveFromCategoryEnabled = isRemoveFromCategoryEnabled,
showTopBar = showTopBar,
enableNavigationButtons = enableNavigationButtons
) )
} }
@Composable @Composable
private fun DefaultTopAppBar( private fun DefaultTopAppBar(
title: String, title: String,
@@ -505,8 +459,6 @@ private fun DefaultTopAppBar(
@Composable @Composable
private fun SearchTopAppBar( private fun SearchTopAppBar(
searchQuery: String,
onQueryChanged: (String) -> Unit,
onCloseSearch: () -> Unit onCloseSearch: () -> Unit
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@@ -518,37 +470,6 @@ private fun SearchTopAppBar(
AppTopAppBar( AppTopAppBar(
modifier = Modifier.height(56.dp), modifier = Modifier.height(56.dp),
title = "TODO", title = "TODO",
additionalContent = {
Box(
modifier = Modifier.fillMaxHeight(),
contentAlignment = Alignment.Center
) {
BasicTextField(
value = searchQuery,
onValueChange = onQueryChanged,
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
textStyle = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.onSurface
),
singleLine = true,
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
decorationBox = { innerTextField ->
Box(contentAlignment = Alignment.CenterStart) {
if (searchQuery.isEmpty()) {
Text(
text = stringResource(R.string.search_vocabulary),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
innerTextField()
}
}
)
}
},
navigationIcon = { navigationIcon = {
IconButton(onClick = onCloseSearch) { IconButton(onClick = onCloseSearch) {
Icon( Icon(
@@ -566,8 +487,6 @@ private fun SearchTopAppBar(
@Composable @Composable
fun SearchTopAppBarPreview() { fun SearchTopAppBarPreview() {
SearchTopAppBar( SearchTopAppBar(
searchQuery = stringResource(R.string.search_query),
onQueryChanged = {},
onCloseSearch = {} onCloseSearch = {}
) )
} }
@@ -670,112 +589,6 @@ fun ContextualTopAppBarPreview() {
) )
} }
@Composable
private fun VocabularyListItem(
item: VocabularyItem,
allLanguages: List<Language>,
isSelected: Boolean,
onItemClick: () -> Unit,
onItemLongClick: () -> Unit,
onDeleteClick: () -> Unit,
modifier: Modifier = Modifier
) {
val languageMap = remember(allLanguages) { allLanguages.associateBy { it.nameResId } }
val langFirst = item.languageFirstId?.let { languageMap[it]?.name } ?: ""
val langSecond = item.languageSecondId?.let { languageMap[it]?.name } ?: ""
OutlinedCard(
modifier = modifier
.fillMaxWidth()
.clip(CardDefaults.shape)
.combinedClickable(
onClick = onItemClick,
onLongClick = onItemLongClick
)
.animateContentSize(),
elevation = CardDefaults.cardElevation(defaultElevation = if (isSelected) 4.dp else 0.dp),
colors = CardDefaults.cardColors(
containerColor = if (isSelected) MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.surface
),
border = BorderStroke(
width = if (isSelected) 1.5.dp else 1.dp,
color = if (isSelected) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.outline.copy(alpha = 0.5f)
)
) {
Row(
modifier = Modifier.padding(horizontal = 4.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
LanguageRow(word = item.wordFirst, language = langFirst)
HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f))
LanguageRow(word = item.wordSecond, language = langSecond)
}
Box(
modifier = Modifier.padding(4.dp),
contentAlignment = Alignment.Center
) {
Crossfade(targetState = isSelected, label = "action-icon-fade") { selected ->
if (selected) {
Icon(
imageVector = AppIcons.Check,
contentDescription = "Selected",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp)
)
} else {
IconButton(onClick = onDeleteClick) {
Icon(
imageVector = AppIcons.Delete,
contentDescription = stringResource(id = R.string.label_delete),
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
)
}
}
}
}
}
}
}
@Composable
private fun LanguageRow(word: String, language: String) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 2.dp)) {
Text(
text = insertBreakOpportunities(word),
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.weight(0.7f)
)
Spacer(Modifier.width(8.dp))
Text(
text = language,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
modifier = Modifier.weight(0.3f)
)
}
}
@ThemePreviews
@Composable
fun LanguageRowPreview() {
LanguageRow(
word = "Hello",
language = "English"
)
}
@Composable @Composable
private fun FilterSortBottomSheet( private fun FilterSortBottomSheet(
@@ -785,8 +598,7 @@ private fun FilterSortBottomSheet(
onDismiss: () -> Unit, onDismiss: () -> Unit,
onApplyFilters: (VocabularyFilterState) -> Unit, onApplyFilters: (VocabularyFilterState) -> Unit,
hideCategory: Boolean = false, hideCategory: Boolean = false,
hideStage: Boolean = false, hideStage: Boolean = false
categoryViewModel: CategoryViewModel
) { ) {
var selectedStage by rememberSaveable { mutableStateOf(currentFilterState.selectedStage) } var selectedStage by rememberSaveable { mutableStateOf(currentFilterState.selectedStage) }
var dueTodayOnly by rememberSaveable { mutableStateOf(currentFilterState.dueTodayOnly) } var dueTodayOnly by rememberSaveable { mutableStateOf(currentFilterState.dueTodayOnly) }
@@ -931,20 +743,3 @@ private fun FilterSortBottomSheet(
} }
} }
@ThemePreviews
@Composable
fun FilterSortBottomSheetPreview() {
val activity = LocalContext.current.findActivity()
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
val categoryViewModel: CategoryViewModel = hiltViewModel(viewModelStoreOwner = activity)
FilterSortBottomSheet(
currentFilterState = VocabularyFilterState(),
languageViewModel = languageViewModel,
languagesPresent = emptyList(),
onDismiss = {},
onApplyFilters = {},
hideCategory = false,
hideStage = false,
categoryViewModel = categoryViewModel
)
}

View File

@@ -1120,4 +1120,22 @@
<string name="label_stats">Stats</string> <string name="label_stats">Stats</string>
<string name="label_library">Library</string> <string name="label_library">Library</string>
<string name="label_edit">Edit</string> <string name="label_edit">Edit</string>
<string name="label_total_wordss">Total Words</string>
<string name="label_new_words">New Words</string>
<string name="desc_expand_your_vocabulary">Expand your vocabulary</string>
<string name="label_settings">Settings</string>
<string name="label_2d_days">%1$d Days</string>
<string name="label_current_streak">Current Streak</string>
<string name="label_daily_goal">Daily Goal</string>
<string name="text_desc_no_activity_data_available">No activity data available</string>
<string name="label_see_history">See History</string>
<string name="label_weekly_progress">Weekly Progress</string>
<string name="cd_go">Go</string>
<string name="label_aapply_filters">Apply Filters</string>
<string name="label_sort_by">Sort By</string>
<string name="label_reset">Reset</string>
<string name="label_filter_cards">Filter Cards</string>
<string name="text_desc_organize_vocabulary_groups">Organize Your Vocabulary in Groups</string>
<string name="text_add_new_word_to_list">Extract a New Word to Your List</string>
<string name="cd_scroll_to_top">Scroll to top</string>
</resources> </resources>