Refactor the dictionary and corrector navigation by promoting the Corrector to a top-level destination and removing the tabbed MainDictionaryScreen.
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Screen>()
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,6 +24,11 @@ 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(
|
||||
@@ -29,6 +38,7 @@ fun DictionaryScreen(
|
||||
languageViewModel = languageViewModel,
|
||||
onNavigateToOptions = onNavigateToOptions
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
||||
@@ -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<TabItem> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
@@ -1171,4 +1171,6 @@
|
||||
<!-- Explore Packs Hint -->
|
||||
<string name="hint_explore_packs_title">About Vocabulary Packs</string>
|
||||
<string name="label_import_csv_or_lists">Import Lists or CSV</string>
|
||||
<string name="label_corrector">Corrector</string>
|
||||
<string name="label_correction">Correction</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user