From cfd71162a0729dc815a53f557aff02d73273baa7 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Fri, 20 Feb 2026 00:03:19 +0100 Subject: [PATCH] Refactor the dictionary and corrector navigation by promoting the Corrector to a top-level destination and removing the tabbed `MainDictionaryScreen`. --- .../gaudian/translator/view/MainActivity.kt | 41 +--- .../eu/gaudian/translator/view/Navigation.kt | 223 +++++------------- .../view/composable/BottomNavigationBar.kt | 15 +- .../view/dictionary/CorrectionScreen.kt | 26 +- .../view/dictionary/DictionaryScreen.kt | 28 ++- .../view/dictionary/MainDictionaryScreen.kt | 97 -------- app/src/main/res/values/strings.xml | 2 + 7 files changed, 114 insertions(+), 318 deletions(-) delete mode 100644 app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt diff --git a/app/src/main/java/eu/gaudian/translator/view/MainActivity.kt b/app/src/main/java/eu/gaudian/translator/view/MainActivity.kt index 0da572e..747478b 100644 --- a/app/src/main/java/eu/gaudian/translator/view/MainActivity.kt +++ b/app/src/main/java/eu/gaudian/translator/view/MainActivity.kt @@ -82,19 +82,14 @@ val LocalConnectionConfigured = compositionLocalOf { true } @AndroidEntryPoint class MainActivity : ComponentActivity() { - - private val settingsViewModel: SettingsViewModel by viewModels() private var isReady = false private var isUiLoaded = false - private var isInitializing = true override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen().apply { - // The splash screen will now correctly wait until isReady is true setKeepOnScreenCondition { !isReady } } - super.onCreate(savedInstanceState) lifecycleScope.launch { @@ -104,28 +99,22 @@ class MainActivity : ComponentActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) - // Show UI immediately and load data in background setContent { AppTheme(settingsViewModel = settingsViewModel) { TranslatorApp(settingsViewModel = settingsViewModel) } } - // Mark UI as loaded immediately after setContent isUiLoaded = true - - // Start initialization in background without blocking UI initializeData() } private fun initializeData() { lifecycleScope.launch(Dispatchers.IO) { - // Get repositories from the Application instance (lazy initialization) val myApp = application as MyApplication val languageRepository = myApp.languageRepository val apiRepository = myApp.apiRepository - // Perform initialization in parallel where possible val languageJob = launch { languageRepository.initializeDefaultLanguages() languageRepository.initializeAllLanguages() @@ -135,13 +124,10 @@ class MainActivity : ComponentActivity() { apiRepository.initialInit() } - // Wait for both to complete languageJob.join() apiJob.join() - // Signal readiness after all work is done. isReady = true - isInitializing = false } } } @@ -149,10 +135,7 @@ class MainActivity : ComponentActivity() { @Suppress("AssignedValueIsNeverRead") @SuppressLint("LocalContextResourcesRead") @Composable -fun TranslatorApp( - settingsViewModel: SettingsViewModel -) { - +fun TranslatorApp(settingsViewModel: SettingsViewModel) { val activity = LocalContext.current.findActivity() val statusViewModel: StatusViewModel = hiltViewModel(activity) val statusMessageService = StatusMessageService @@ -179,7 +162,6 @@ fun TranslatorApp( showExitDialog = true } - if (showExitDialog) { AppAlertDialog( onDismissRequest = { showExitDialog = false }, @@ -188,7 +170,6 @@ fun TranslatorApp( confirmButton = { TextButton(onClick = { showExitDialog = false - // Minimize the app similar to default back at root behavior activity.moveTaskToBack(true) }) { Text(stringResource(R.string.quit)) @@ -202,7 +183,6 @@ fun TranslatorApp( ) } - // Check for app updates and show "What's New" dialog if needed var showWhatsNewDialog by remember { mutableStateOf(false) } val context = LocalContext.current val changelogEntries = context.resources.getStringArray(R.array.changelog_entries) @@ -210,7 +190,6 @@ fun TranslatorApp( LaunchedEffect(Unit) { try { - // Only check for updates if the intro is completed if (introCompleted) { val currentVersion = BuildConfig.VERSION_NAME val hasSeenCurrentVersion = settingsViewModel.hasSeenCurrentVersion(currentVersion) @@ -260,7 +239,8 @@ fun TranslatorApp( Screen.Translation.route, Screen.Dictionary.route, Screen.Exercises.route, - Screen.Settings.route + Screen.Settings.route, + Screen.Corrector.route ) } == true val isBottomBarHidden = isHiddenByHierarchy || currentRoute in setOf( @@ -283,12 +263,11 @@ fun TranslatorApp( Screen.Translation, Screen.Dictionary, Screen.Settings, - Screen.Exercises + Screen.Exercises, + Screen.Corrector ) - // Always reset the selected section to its root and clear back stack between sections if (inSameSection) { - // If already within the same section, ensure we are at its graph root navController.navigate(screen.route) { popUpTo(screen.route) { inclusive = false @@ -303,9 +282,8 @@ fun TranslatorApp( restoreState = false } } else { - // Switching sections: clear entire back stack to start to avoid back navigation results navController.navigate(screen.route) { - popUpTo(0) { // Pop everything + popUpTo(0) { inclusive = true saveState = false } @@ -340,8 +318,7 @@ fun TranslatorApp( statusState = statusState, navController = navController, onDismiss = { statusMessageService.trigger(StatusAction.HideMessageBar) }, - modifier = Modifier - .fillMaxWidth() + modifier = Modifier.fillMaxWidth() ) AppNavHost( navController = navController, @@ -393,10 +370,8 @@ private fun AppTheme( val window = (view.context as Activity).window val windowInsetsController = WindowInsetsControllerCompat(window, view) - // We must keep this for older Android version!!! @Suppress("DEPRECATION") window.statusBarColor = colorScheme.surface.toArgb() - //Elevation must be the same as BottomNavigationBar @Suppress("DEPRECATION") window.navigationBarColor = colorScheme.surfaceColorAtElevation(8.dp).toArgb() @@ -443,6 +418,4 @@ private fun AppTheme( content() } } - - } diff --git a/app/src/main/java/eu/gaudian/translator/view/Navigation.kt b/app/src/main/java/eu/gaudian/translator/view/Navigation.kt index 570a27a..fc06987 100644 --- a/app/src/main/java/eu/gaudian/translator/view/Navigation.kt +++ b/app/src/main/java/eu/gaudian/translator/view/Navigation.kt @@ -23,9 +23,10 @@ import eu.gaudian.translator.model.VocabularyStage import eu.gaudian.translator.view.categories.CategoryDetailScreen import eu.gaudian.translator.view.categories.CategoryListScreen import eu.gaudian.translator.view.composable.Screen +import eu.gaudian.translator.view.dictionary.CorrectionScreen import eu.gaudian.translator.view.dictionary.DictionaryResultScreen +import eu.gaudian.translator.view.dictionary.DictionaryScreen import eu.gaudian.translator.view.dictionary.EtymologyResultScreen -import eu.gaudian.translator.view.dictionary.MainDictionaryScreen import eu.gaudian.translator.view.home.DailyReviewScreen import eu.gaudian.translator.view.home.HomeScreen import eu.gaudian.translator.view.library.LibraryScreen @@ -78,22 +79,14 @@ fun AppNavHost( navController: NavHostController, modifier: Modifier = Modifier ) { - - - - // 1. Define your Main Tab "Leaf" Routes (the actual start destinations of your graphs) val mainTabRoutes = setOf( Screen.Home.route, Screen.Library.route, Screen.Stats.route, - ) - // Helper to check if a route is a top-level tab - // Note: Routes can be "main_home", "main_library" etc. but mainTabRoutes contains parent routes fun isTabTransition(initial: String?, target: String?): Boolean { if (initial == null || target == null) return false - // Check if either the direct route OR a "main_*" variant is in mainTabRoutes val initialIsTab = mainTabRoutes.contains(initial) || mainTabRoutes.any { route -> initial == "main_${route}" || initial.startsWith("${route}_") @@ -109,45 +102,33 @@ fun AppNavHost( navController = navController, startDestination = Screen.Home.route, modifier = modifier, - - // ENTER TRANSITION enterTransition = { if (isTabTransition(initialState.destination.route, targetState.destination.route)) { - // Tab Switch: Just Fade In (Subtle Scale for modern feel) fadeIn(animationSpec = tween(TRANSITION_DURATION)) + scaleIn(initialScale = 0.96f, animationSpec = tween(TRANSITION_DURATION)) } else { - // Detail Screen: Slide in from Right slideInHorizontally( initialOffsetX = { fullWidth -> (fullWidth * 0.1f).toInt() }, animationSpec = tween(TRANSITION_DURATION, easing = FastOutSlowInEasing) ) + fadeIn(animationSpec = tween(TRANSITION_DURATION)) } }, - - // EXIT TRANSITION exitTransition = { if (isTabTransition(initialState.destination.route, targetState.destination.route)) { - // Tab Switch: Just Fade Out fadeOut(animationSpec = tween(TRANSITION_DURATION)) } else { - // Detail Screen: Slide out to Left slideOutHorizontally( targetOffsetX = { fullWidth -> -(fullWidth * 0.1f).toInt() }, animationSpec = tween(TRANSITION_DURATION, easing = FastOutSlowInEasing) ) + fadeOut(animationSpec = tween(TRANSITION_DURATION)) } }, - - // POP ENTER (Pressing Back) -> Always Slide back from left popEnterTransition = { slideInHorizontally( initialOffsetX = { fullWidth -> -(fullWidth * 0.1f).toInt() }, animationSpec = tween(TRANSITION_DURATION, easing = FastOutSlowInEasing) ) + fadeIn(animationSpec = tween(TRANSITION_DURATION)) }, - - // POP EXIT (Pressing Back) -> Always Slide away to right popExitTransition = { slideOutHorizontally( targetOffsetX = { fullWidth -> (fullWidth * 0.1f).toInt() }, @@ -158,23 +139,18 @@ fun AppNavHost( composable(Screen.Home.route) { HomeScreen(navController = navController) } - composable(NavigationRoutes.DAILY_REVIEW) { DailyReviewScreen(navController = navController) } - composable(NavigationRoutes.NEW_WORD) { NewWordScreen(navController = navController) } - composable(NavigationRoutes.NEW_WORD_REVIEW) { NewWordReviewScreen(navController = navController) } - composable(NavigationRoutes.EXPLORE_PACKS) { ExplorePacksScreen(navController = navController) } - composable( route = "${NavigationRoutes.START_EXERCISE}?categoryId={categoryId}", arguments = listOf( @@ -193,7 +169,6 @@ fun AppNavHost( dueTodayOnly = false ) } - composable(NavigationRoutes.START_EXERCISE_DAILY) { StartExerciseScreen( navController = navController, @@ -201,13 +176,12 @@ fun AppNavHost( dueTodayOnly = true ) } - - // Define all other navigation graphs at the same top level. homeGraph(navController) libraryGraph(navController) statsGraph(navController) translationGraph(navController) dictionaryGraph(navController) + correctorGraph(navController) exerciseGraph(navController) settingsGraph(navController) } @@ -233,9 +207,7 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) { LibraryScreen(navController = navController) } composable("vocabulary_sorting") { - VocabularySortingScreen( - navController = navController - ) + VocabularySortingScreen(navController = navController) } composable("vocabulary_detail/{itemId}") { backStackEntry -> val itemId = backStackEntry.arguments?.getString("itemId")?.toIntOrNull() @@ -252,10 +224,7 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) { composable("dictionary_result/{entryId}") { backStackEntry -> val entryId = backStackEntry.arguments?.getString("entryId")?.toIntOrNull() if (entryId != null) { - DictionaryResultScreen( - entryId = entryId, - navController = navController, - ) + DictionaryResultScreen(entryId = entryId, navController = navController) } else { Text("Error: Invalid Entry ID") } @@ -267,23 +236,16 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) { navController = navController, showDueTodayOnly = showDueTodayOnly, categoryId = categoryId, - onNavigateToItem = { item -> - navController.navigate("vocabulary_detail/${item.id}") - }, + onNavigateToItem = { item -> navController.navigate("vocabulary_detail/${item.id}") }, onNavigateBack = { navController.popBackStack() }, enableNavigationButtons = true ) } composable("language_progress") { - LanguageJourneyScreen( - navController = navController - ) - + LanguageJourneyScreen(navController = navController) } composable(NavigationRoutes.STATS_VOCABULARY_HEATMAP) { - VocabularyHeatmapScreen( - navController = navController, - ) + VocabularyHeatmapScreen(navController = navController) } composable("vocabulary_list/{showDueTodayOnly}/{stage}") { backStackEntry -> val showDueTodayOnly = backStackEntry.arguments?.getString("showDueTodayOnly")?.toBoolean() ?: false @@ -291,14 +253,11 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) { val stage = stageString?.let { if (it.equals("null", ignoreCase = true)) null else VocabularyStage.valueOf(it) } - AllCardsListScreen( navController = navController, showDueTodayOnly = showDueTodayOnly, stage = stage, - onNavigateToItem = { item -> - navController.navigate("vocabulary_detail/${item.id}") - }, + onNavigateToItem = { item -> navController.navigate("vocabulary_detail/${item.id}") }, onNavigateBack = { navController.popBackStack() }, categoryId = 0, enableNavigationButtons = true @@ -311,22 +270,15 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) { navArgument("categories") { type = NavType.StringType; nullable = true }, navArgument("stages") { type = NavType.StringType; nullable = true }, navArgument("languages") { type = NavType.StringType; nullable = true }, - navArgument("dailyOnly") { - type = NavType.BoolType - defaultValue = false - }, + navArgument("dailyOnly") { type = NavType.BoolType; defaultValue = false } ) ) { backStackEntry -> val arguments = backStackEntry.arguments - val dailyOnly = arguments?.getBoolean("dailyOnly") ?: false - val categoryIds = arguments?.getString("categories") val stageNames = arguments?.getString("stages") val languageIds = arguments?.getString("languages") - val dailyOnlyJson = "{\"dailyOnly\": $dailyOnly}" - VocabularyExerciseHostScreen( categoryIdsAsJson = categoryIds, stageNamesAsJson = stageNames, @@ -336,13 +288,7 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) { navController = navController ) } - composable( - route = "vocabulary_exercise/{dailyOnly}?", - arguments = listOf( - navArgument("dailyOnly") { type = NavType.BoolType }, - ) - ) { _ -> - + composable("vocabulary_exercise/{dailyOnly}?", arguments = listOf(navArgument("dailyOnly") { type = NavType.BoolType })) { _ -> VocabularyExerciseHostScreen( categoryIdsAsJson = null, stageNamesAsJson = null, @@ -352,34 +298,18 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) { dailyOnlyAsJson = "{\"dailyOnly\": true}" ) } - composable( - "stage_detail/{stage}", - arguments = listOf( - navArgument("stage") { - type = NavType.EnumType(VocabularyStage::class.java) - } - ) - ) - - { backStackEntry -> - @Suppress("DEPRECATION") val stage = backStackEntry.arguments?.getSerializable("stage") as VocabularyStage - //NOTE: can ignore warning for now, once moved away from min SDK 28, use: - // val stage = backStackEntry.arguments?.getSerializable("stage", VocabularyStage::class.java) - StageDetailScreen( - navController = navController, - stage = stage - ) + composable("stage_detail/{stage}", arguments = listOf(navArgument("stage") { type = NavType.EnumType(VocabularyStage::class.java) })) { backStackEntry -> + @Suppress("DEPRECATION") + val stage = backStackEntry.arguments?.getSerializable("stage") as VocabularyStage + StageDetailScreen(navController = navController, stage = stage) } composable("category_detail/{categoryId}") { backStackEntry -> val categoryId = backStackEntry.arguments?.getString("categoryId")?.toIntOrNull() - if (categoryId != null) { CategoryDetailScreen( categoryId = categoryId, onBackClick = { navController.popBackStack() }, - onNavigateToItem = { item -> - navController.navigate("vocabulary_detail/${item.id}") - }, + onNavigateToItem = { item -> navController.navigate("vocabulary_detail/${item.id}") }, navController = navController ) } @@ -387,37 +317,22 @@ fun NavGraphBuilder.libraryGraph(navController: NavHostController) { composable("category_list_screen") { CategoryListScreen( onNavigateBack = { navController.popBackStack() }, - onCategoryClicked = { categoryId -> - navController.navigate("category_detail/$categoryId") - } + onCategoryClicked = { categoryId -> navController.navigate("category_detail/$categoryId") } ) } - composable( - route = "vocabulary_sorting?mode={mode}", // Route now accepts an optional 'mode' - arguments = listOf( - navArgument("mode") { // Define the argument - type = NavType.StringType - nullable = true - } - ) - ) { backStackEntry -> + composable("vocabulary_sorting?mode={mode}", arguments = listOf(navArgument("mode") { type = NavType.StringType; nullable = true })) { backStackEntry -> VocabularySortingScreen( navController = navController, - // Pass the argument to the screen initialFilterMode = backStackEntry.arguments?.getString("mode") ) } composable("no_grammar_items") { - NoGrammarItemsScreen( - navController = navController - ) + NoGrammarItemsScreen(navController = navController) } } } -fun NavGraphBuilder.statsGraph( - navController: NavHostController, -) { +fun NavGraphBuilder.statsGraph(navController: NavHostController) { navigation( startDestination = "main_stats", route = Screen.Stats.route @@ -426,9 +341,7 @@ fun NavGraphBuilder.statsGraph( StatsScreen(navController = navController) } composable("stats/vocabulary_sorting") { - VocabularySortingScreen( - navController = navController - ) + VocabularySortingScreen(navController = navController) } composable("stats/vocabulary_list/{showDueTodayOnly}/{categoryId}") { backStackEntry -> val showDueTodayOnly = backStackEntry.arguments?.getString("showDueTodayOnly")?.toBoolean() ?: false @@ -437,22 +350,16 @@ fun NavGraphBuilder.statsGraph( navController = navController, showDueTodayOnly = showDueTodayOnly, categoryId = categoryId, - onNavigateToItem = { item -> - navController.navigate("vocabulary_detail/${item.id}") - }, + onNavigateToItem = { item -> navController.navigate("vocabulary_detail/${item.id}") }, onNavigateBack = { navController.popBackStack() }, enableNavigationButtons = true ) } composable(NavigationRoutes.STATS_LANGUAGE_PROGRESS) { - LanguageJourneyScreen( - navController = navController - ) + LanguageJourneyScreen(navController = navController) } composable(NavigationRoutes.STATS_VOCABULARY_HEATMAP) { - VocabularyHeatmapScreen( - navController = navController, - ) + VocabularyHeatmapScreen(navController = navController) } composable("stats/vocabulary_list/{showDueTodayOnly}/{stage}") { backStackEntry -> val showDueTodayOnly = backStackEntry.arguments?.getString("showDueTodayOnly")?.toBoolean() ?: false @@ -460,14 +367,11 @@ fun NavGraphBuilder.statsGraph( val stage = stageString?.let { if (it.equals("null", ignoreCase = true)) null else VocabularyStage.valueOf(it) } - AllCardsListScreen( navController = navController, showDueTodayOnly = showDueTodayOnly, stage = stage, - onNavigateToItem = { item -> - navController.navigate("vocabulary_detail/${item.id}") - }, + onNavigateToItem = { item -> navController.navigate("vocabulary_detail/${item.id}") }, onNavigateBack = { navController.popBackStack() }, categoryId = 0, enableNavigationButtons = true @@ -475,14 +379,11 @@ fun NavGraphBuilder.statsGraph( } composable("stats/category_detail/{categoryId}") { backStackEntry -> val categoryId = backStackEntry.arguments?.getString("categoryId")?.toIntOrNull() - if (categoryId != null) { CategoryDetailScreen( categoryId = categoryId, onBackClick = { navController.popBackStack() }, - onNavigateToItem = { item -> - navController.navigate("vocabulary_detail/${item.id}") - }, + onNavigateToItem = { item -> navController.navigate("vocabulary_detail/${item.id}") }, navController = navController ) } @@ -490,29 +391,14 @@ fun NavGraphBuilder.statsGraph( composable("stats/category_list_screen") { CategoryListScreen( onNavigateBack = { navController.popBackStack() }, - onCategoryClicked = { categoryId -> - navController.navigate("stats/category_detail/$categoryId") - } + onCategoryClicked = { categoryId -> navController.navigate("stats/category_detail/$categoryId") } ) } - composable( - route = "stats/vocabulary_sorting?mode={mode}", - arguments = listOf( - navArgument("mode") { - type = NavType.StringType - nullable = true - } - ) - ) { backStackEntry -> - VocabularySortingScreen( - navController = navController, - initialFilterMode = backStackEntry.arguments?.getString("mode") - ) + composable("stats/vocabulary_sorting?mode={mode}", arguments = listOf(navArgument("mode") { type = NavType.StringType; nullable = true })) { backStackEntry -> + VocabularySortingScreen(navController = navController, initialFilterMode = backStackEntry.arguments?.getString("mode")) } composable("stats/no_grammar_items") { - NoGrammarItemsScreen( - navController = navController - ) + NoGrammarItemsScreen(navController = navController) } } } @@ -539,15 +425,16 @@ fun NavGraphBuilder.dictionaryGraph(navController: NavHostController) { route = Screen.Dictionary.route ) { composable("main_dictionary") { - MainDictionaryScreen(navController = navController) + DictionaryScreen( + navController = navController, + onEntryClick = { entry -> navController.navigate("dictionary_result/${entry.id}") }, + onNavigateToOptions = { navController.navigate("dictionary_options") } + ) } composable("dictionary_result/{entryId}") { backStackEntry -> val entryId = backStackEntry.arguments?.getString("entryId")?.toIntOrNull() if (entryId != null) { - DictionaryResultScreen( - entryId = entryId, - navController = navController, - ) + DictionaryResultScreen(entryId = entryId, navController = navController) } else { Text("Error: Invalid Entry ID") } @@ -558,43 +445,39 @@ fun NavGraphBuilder.dictionaryGraph(navController: NavHostController) { composable("etymology_result/{word}/{languageCode}") { backStackEntry -> val word = backStackEntry.arguments?.getString("word") ?: "" val languageCode = backStackEntry.arguments?.getString("languageCode")?.toIntOrNull() ?: 1 - EtymologyResultScreen( - navController = navController, - word = word, - languageCode = languageCode - ) + EtymologyResultScreen(navController = navController, word = word, languageCode = languageCode) } + } +} +fun NavGraphBuilder.correctorGraph(navController: NavHostController) { + navigation( + startDestination = "main_corrector", + route = Screen.Corrector.route + ) { + composable("main_corrector") { + CorrectionScreen(navController = navController) + } } } @OptIn(androidx.compose.animation.ExperimentalAnimationApi::class) -fun NavGraphBuilder.exerciseGraph( - navController: NavHostController, -) { +fun NavGraphBuilder.exerciseGraph(navController: NavHostController) { navigation( startDestination = "main_exercise", route = Screen.Exercises.route ) { composable("main_exercise") { - MainExerciseScreen( - navController = navController, - ) + MainExerciseScreen(navController = navController) } composable("exercise_session") { - ExerciseSessionScreen( - navController = navController, - ) + ExerciseSessionScreen(navController = navController) } composable("youtube_exercise") { - YouTubeExerciseScreen( - navController = navController - ) + YouTubeExerciseScreen(navController = navController) } composable("youtube_browse") { - YouTubeBrowserScreen( - navController = navController, - ) + YouTubeBrowserScreen(navController = navController) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/BottomNavigationBar.kt b/app/src/main/java/eu/gaudian/translator/view/composable/BottomNavigationBar.kt index 38e7934..fe72784 100644 --- a/app/src/main/java/eu/gaudian/translator/view/composable/BottomNavigationBar.kt +++ b/app/src/main/java/eu/gaudian/translator/view/composable/BottomNavigationBar.kt @@ -11,6 +11,7 @@ import androidx.compose.animation.core.spring import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -77,6 +78,7 @@ sealed class Screen( object Settings : Screen("settings", R.string.title_settings, AppIcons.SettingsFilled, AppIcons.SettingsOutlined) object More : Screen("more", R.string.label_more, AppIcons.MoreVert, AppIcons.MoreHorizontal) object Dictionary : Screen("dictionary", R.string.label_dictionary, AppIcons.DictionaryFilled, AppIcons.DictionaryOutlined) + object Corrector : Screen("corrector", R.string.title_corrector, AppIcons.SpellCheck, AppIcons.SpellCheck) object Exercises : Screen("exercises", R.string.label_exercises, AppIcons.DictionaryFilled, AppIcons.DictionaryOutlined) companion object { @@ -88,6 +90,7 @@ sealed class Screen( val items = mutableListOf() items.add(Translation) items.add(Dictionary) + items.add(Corrector) items.add(Settings) if (showExperimental) { items.add(Exercises) @@ -258,7 +261,7 @@ fun BottomNavigationBar( .background( brush = Brush.radialGradient( colors = listOf( - MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), + MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f), Color.Transparent ) ), @@ -271,6 +274,12 @@ fun BottomNavigationBar( modifier = Modifier .size(playButtonSize) .clip(CircleShape) + // CHANGED: Added a border to give the button definition + .border( + width = 4.dp, // Adjust this thickness to your liking + color = MaterialTheme.colorScheme.surfaceVariant, // Creates a nice "cutout" separation + shape = CircleShape + ) .background(MaterialTheme.colorScheme.primaryContainer) .clickable { haptic.performHapticFeedback(HapticFeedbackType.LongPress) @@ -281,7 +290,7 @@ fun BottomNavigationBar( Icon( imageVector = Icons.Filled.PlayArrow, contentDescription = "Play", - tint = Color.White, + tint = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.size(32.dp) ) } @@ -400,4 +409,4 @@ fun BottomNavigationBarPreview() { ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/CorrectionScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/CorrectionScreen.kt index 4d799d4..c7e9b5d 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/CorrectionScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dictionary/CorrectionScreen.kt @@ -60,12 +60,16 @@ import androidx.compose.ui.text.input.TextFieldValue 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.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.AppOutlinedButton import eu.gaudian.translator.view.composable.AppSwitch +import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.DictionaryLanguageDropDown import eu.gaudian.translator.view.composable.DropdownDefaults import eu.gaudian.translator.view.composable.LargeDropdownMenuItem @@ -73,12 +77,15 @@ import eu.gaudian.translator.viewmodel.CorrectionViewModel import eu.gaudian.translator.viewmodel.LanguageViewModel import kotlinx.coroutines.launch -// 1. STATEFUL COMPONENT (Connects to ViewModels) @Composable fun CorrectionScreen( - correctionViewModel: CorrectionViewModel, - languageViewModel: LanguageViewModel + navController: NavController + ) { + + val activity = LocalContext.current.findActivity() + val correctionViewModel: CorrectionViewModel = hiltViewModel(activity) + val languageViewModel : LanguageViewModel = hiltViewModel(activity) val textFieldValue by correctionViewModel.textFieldValue.collectAsState() val explanation by correctionViewModel.explanation.collectAsState() val isLoading by correctionViewModel.isLoading.collectAsState() @@ -89,6 +96,15 @@ fun CorrectionScreen( val successColor = MaterialTheme.semanticColors.success + Column(){ + + AppTopAppBar( + title = stringResource(R.string.label_correction), + onNavigateBack = { + navController.popBackStack() + }, + ) + CorrectionScreenContent( textFieldValue = textFieldValue, explanation = explanation, @@ -113,6 +129,7 @@ fun CorrectionScreen( ) } ) + } } // 2. STATELESS COMPONENT (Handles UI Layout) @@ -304,7 +321,6 @@ fun CorrectionScreenContent( - Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, @@ -575,4 +591,4 @@ private fun CorrectionScreenResultsPreview() { } ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryScreen.kt index 6c9d9fa..d4263d8 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dictionary/DictionaryScreen.kt @@ -1,12 +1,16 @@ package eu.gaudian.translator.view.dictionary +import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController +import eu.gaudian.translator.R import eu.gaudian.translator.utils.findActivity +import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.viewmodel.DictionaryViewModel import eu.gaudian.translator.viewmodel.LanguageViewModel @@ -20,15 +24,21 @@ fun DictionaryScreen( val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity) val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) + Column { + AppTopAppBar( + title = stringResource(R.string.label_dictionary), + onNavigateBack = { navController.popBackStack() } + ) - // Use the new refactored component - DictionaryScreenContent( - navController = navController, - onEntryClick = onEntryClick, - dictionaryViewModel = dictionaryViewModel, - languageViewModel = languageViewModel, - onNavigateToOptions = onNavigateToOptions - ) + // Use the new refactored component + DictionaryScreenContent( + navController = navController, + onEntryClick = onEntryClick, + dictionaryViewModel = dictionaryViewModel, + languageViewModel = languageViewModel, + onNavigateToOptions = onNavigateToOptions + ) + } } @Preview @@ -40,4 +50,4 @@ fun DictionaryScreenPreview() { onEntryClick = {}, onNavigateToOptions = {} ) -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt deleted file mode 100644 index 0b58695..0000000 --- a/app/src/main/java/eu/gaudian/translator/view/dictionary/MainDictionaryScreen.kt +++ /dev/null @@ -1,97 +0,0 @@ -@file:Suppress("HardCodedStringLiteral") - -package eu.gaudian.translator.view.dictionary - -import androidx.compose.foundation.layout.Column -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -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.res.stringResource -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.navigation.NavController -import androidx.navigation.compose.rememberNavController -import eu.gaudian.translator.R -import eu.gaudian.translator.ui.theme.ThemePreviews -import eu.gaudian.translator.utils.findActivity -import eu.gaudian.translator.view.composable.AppIcons -import eu.gaudian.translator.view.composable.AppTabLayout -import eu.gaudian.translator.view.composable.Screen -import eu.gaudian.translator.view.composable.TabItem -import eu.gaudian.translator.viewmodel.CorrectionViewModel -import eu.gaudian.translator.viewmodel.DictionaryViewModel -import eu.gaudian.translator.viewmodel.LanguageViewModel - - -@Composable -private fun getDictionaryTabs(): List { - return listOf( - DictionaryTab(stringResource(R.string.label_dictionary), AppIcons.Dictionary), - DictionaryTab(stringResource(R.string.title_corrector), AppIcons.Check) - ) -} -private data class DictionaryTab(override val title: String, override val icon: ImageVector) : - TabItem - -@Composable -fun MainDictionaryScreen( - navController: NavController - ) { - - - val activity = LocalContext.current.findActivity() - - val dictionaryViewModel: DictionaryViewModel = hiltViewModel(viewModelStoreOwner = activity) - val correctionViewModel: CorrectionViewModel = hiltViewModel(viewModelStoreOwner = activity) - val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) - val dictionaryTabs = getDictionaryTabs() - - - var selectedTab by remember { mutableStateOf(dictionaryTabs[0]) } - Column { - AppTabLayout( - tabs = dictionaryTabs, - selectedTab = selectedTab, - onTabSelected = { selectedTab = it }, - onNavigateBack = { - if (!navController.popBackStack()) { - navController.navigate(Screen.Home.route) { - launchSingleTop = true - restoreState = false - } - } - } - ) - - when (selectedTab) { - dictionaryTabs[0] -> DictionaryScreen( - navController = navController, - onEntryClick = { entry -> - // Set flag indicating navigation is from external source (not DictionaryResultScreen) - dictionaryViewModel.setNavigatingFromDictionaryResult(false) - navController.navigate("dictionary_result/${entry.id}") - }, - onNavigateToOptions = { - navController.navigate("dictionary_options") - } - ) - dictionaryTabs[1] -> CorrectionScreen( - correctionViewModel = correctionViewModel, - languageViewModel = languageViewModel - ) - } - } -} - -@ThemePreviews -@Composable -fun DictionaryHostScreenPreview() { - val navController = rememberNavController() - - MainDictionaryScreen( - navController = navController, - ) -} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a4aa002..0d4d68d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1171,4 +1171,6 @@ About Vocabulary Packs Import Lists or CSV + Corrector + Correction