From 7fccda7f7769da89f5a1df1a036a79a78e8ba538 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:48:52 +0100 Subject: [PATCH] implement `HomeScreen` and refactor navigation to include a separate Home and Translation section --- app/src/main/AndroidManifest.xml | 11 - .../eu/gaudian/translator/CorrectActivity.kt | 45 --- .../eu/gaudian/translator/view/Navigation.kt | 22 +- .../view/composable/BottomNavigationBar.kt | 7 +- .../translator/view/home/HomeScreen.kt | 359 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 6 files changed, 382 insertions(+), 63 deletions(-) delete mode 100644 app/src/main/java/eu/gaudian/translator/CorrectActivity.kt create mode 100644 app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1254ca1..964c563 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,17 +32,6 @@ - - - - - - - - - diff --git a/app/src/main/java/eu/gaudian/translator/CorrectActivity.kt b/app/src/main/java/eu/gaudian/translator/CorrectActivity.kt deleted file mode 100644 index c730db2..0000000 --- a/app/src/main/java/eu/gaudian/translator/CorrectActivity.kt +++ /dev/null @@ -1,45 +0,0 @@ -@file:Suppress("HardCodedStringLiteral") - -package eu.gaudian.translator - -import android.content.Intent -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.material3.Text -import androidx.compose.ui.res.stringResource -import eu.gaudian.translator.utils.Log - -class CorrectActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val intent = intent - val action = intent.action - val type = intent.type - - if (Intent.ACTION_SEND == action && type == "text/plain") { - val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT) - if (sharedText != null) { - Log.d("EditActivity", "Received text: $sharedText") - setContent { - Text(stringResource(R.string.editing_text, sharedText)) - - } - } else { - Log.e("EditActivity", getString(R.string.no_text_received)) - setContent { - Text(stringResource(R.string.error_no_text_to_edit)) - } - } - } else { - Log.d("EditActivity", "Not launched with ACTION_SEND") - setContent { - Text(stringResource(R.string.not_launched_with_text_to_edit)) - } - } - - } - - -} \ No newline at end of file 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 019fe95..40e86b5 100644 --- a/app/src/main/java/eu/gaudian/translator/view/Navigation.kt +++ b/app/src/main/java/eu/gaudian/translator/view/Navigation.kt @@ -28,6 +28,7 @@ import eu.gaudian.translator.view.exercises.ExerciseSessionScreen import eu.gaudian.translator.view.exercises.MainExerciseScreen import eu.gaudian.translator.view.exercises.YouTubeBrowserScreen import eu.gaudian.translator.view.exercises.YouTubeExerciseScreen +import eu.gaudian.translator.view.home.HomeScreen import eu.gaudian.translator.view.settings.DictionaryOptionsScreen import eu.gaudian.translator.view.settings.SettingsRoutes import eu.gaudian.translator.view.settings.TranslationSettingsScreen @@ -57,8 +58,8 @@ fun AppNavHost( // 1. Define your Main Tab "Leaf" Routes (the actual start destinations of your graphs) val mainTabRoutes = setOf( - Screen.Home.route, // "home" or "main_translation" depending on which one you actually navigate to - "main_translation", + Screen.Home.route, + Screen.Translation.route, "main_dictionary", "main_vocabulary", "main_exercise", @@ -121,10 +122,11 @@ fun AppNavHost( } ) { composable(Screen.Home.route) { - TranslationScreen(navController = navController) + HomeScreen(navController = navController) } // Define all other navigation graphs at the same top level. + homeGraph(navController) translationGraph(navController) dictionaryGraph(navController) vocabularyGraph(navController) @@ -132,11 +134,23 @@ fun AppNavHost( settingsGraph(navController) } } + +fun NavGraphBuilder.homeGraph(navController: NavHostController) { + navigation( + startDestination = "main_home", + route = Screen.Home.route + ) { + composable("main_home") { + HomeScreen(navController = navController) + } + } +} + @OptIn(androidx.compose.animation.ExperimentalAnimationApi::class) fun NavGraphBuilder.translationGraph(navController: NavHostController) { navigation( startDestination = "main_translation", - route = Screen.Home.route + route = Screen.Translation.route ) { composable("main_translation") { TranslationScreen(navController = navController) 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 373271f..31466f6 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 @@ -48,7 +48,8 @@ sealed class Screen( val selectedIcon: ImageVector, val unselectedIcon: ImageVector ) { - object Home : Screen("home", R.string.label_translation, AppIcons.TranslateFilled, AppIcons.TranslateOutlined) + object Home : Screen("home", R.string.label_home, AppIcons.Home, AppIcons.Home) + object Translation : Screen("translation", R.string.label_translation, AppIcons.TranslateFilled, AppIcons.TranslateOutlined) object Dictionary : Screen("dictionary", R.string.label_dictionary, AppIcons.DictionaryFilled, AppIcons.DictionaryOutlined) object Exercises : Screen("exercises", R.string.label_exercises, AppIcons.DictionaryFilled, AppIcons.DictionaryOutlined) object Vocabulary : Screen("vocabulary", R.string.label_vocabulary, AppIcons.VocabularyFilled, AppIcons.VocabularyOutlined) @@ -56,9 +57,9 @@ sealed class Screen( companion object { fun getAllScreens(showExperimental: Boolean = false): List { - val screens = mutableListOf(Home, Dictionary, Vocabulary, Settings) + val screens = mutableListOf(Home, Translation, Dictionary, Vocabulary, Settings) if (showExperimental) { - screens.add(2, Exercises) + screens.add(3, Exercises) } return screens } diff --git a/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt b/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt new file mode 100644 index 0000000..c38a8fc --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt @@ -0,0 +1,359 @@ +package eu.gaudian.translator.view.home + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddCircle +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.LocalFireDepartment +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Psychology +import androidx.compose.material.icons.filled.TrendingUp +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController + +@Composable +fun HomeScreen( + navController: NavHostController, + modifier: Modifier = Modifier +) { + // A Box with TopCenter alignment keeps the UI centered on wide screens (tablets/foldables) + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.TopCenter + ) { + LazyColumn( + modifier = Modifier + .widthIn(max = 700.dp) // Prevents extreme stretching on tablets + .fillMaxSize() + .padding(horizontal = 20.dp, vertical = 24.dp), + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + item { TopProfileSection() } + item { StreakAndGoalSection() } + item { + ActionCard( + title = "Daily Review", + subtitle = "42 words need attention", + icon = Icons.Default.Psychology, + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + } + item { + ActionCard( + title = "New Words", + subtitle = "Expand your vocabulary", + icon = Icons.Default.AddCircle, + containerColor = MaterialTheme.colorScheme.surfaceVariant, + contentColor = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + item { WeeklyProgressSection() } + item { BottomStatsSection() } + + // Bottom padding for edge-to-edge screens + item { Spacer(modifier = Modifier.height(24.dp)) } + } + } +} + +@Composable +fun TopProfileSection() { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + // Dummy Avatar + Box( + modifier = Modifier + .size(56.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant), + contentAlignment = Alignment.Center + ) { + Icon(Icons.Default.Person, contentDescription = "Profile", tint = MaterialTheme.colorScheme.onSurfaceVariant) + } + + Spacer(modifier = Modifier.width(16.dp)) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Welcome back,", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) + ) + Text( + text = "Alex Rivera 👋", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + } + + IconButton( + onClick = { /* TODO: Open notifications */ }, + modifier = Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + ) { + Icon( + imageVector = Icons.Default.Notifications, + contentDescription = "Notifications", + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +@Composable +fun StreakAndGoalSection() { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Streak Card + StatCard( + modifier = Modifier.weight(1f), + icon = Icons.Default.LocalFireDepartment, + title = "7 Days", + subtitle = "CURRENT STREAK" + ) + // Goal Card + GoalCard( + modifier = Modifier.weight(1f), + progress = 0.7f, + title = "14 / 20", + subtitle = "DAILY GOAL" + ) + } +} + +@Composable +fun StatCard( + modifier: Modifier = Modifier, + icon: ImageVector, + title: String, + subtitle: String +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column( + modifier = Modifier.padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(32.dp) + ) + Spacer(modifier = Modifier.height(12.dp)) + Text(text = title, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = subtitle, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)) + } + } +} + +@Composable +fun GoalCard( + modifier: Modifier = Modifier, + progress: Float, + title: String, + subtitle: String +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column( + modifier = Modifier.padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box(contentAlignment = Alignment.Center) { + CircularProgressIndicator( + progress = { progress }, + modifier = Modifier.size(48.dp), + strokeWidth = 4.dp, + color = MaterialTheme.colorScheme.primary, + trackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f) + ) + Text(text = "${(progress * 100).toInt()}%", style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Bold) + } + Spacer(modifier = Modifier.height(12.dp)) + Text(text = title, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = subtitle, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)) + } + } +} + +@Composable +fun ActionCard( + title: String, + subtitle: String, + icon: ImageVector, + containerColor: Color, + contentColor: Color +) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = containerColor, contentColor = contentColor) + ) { + Row( + modifier = Modifier.padding(20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(40.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + Column(modifier = Modifier.weight(1f)) { + Text(text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + Text(text = subtitle, style = MaterialTheme.typography.bodyMedium, color = contentColor.copy(alpha = 0.8f)) + } + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = "Go", + modifier = Modifier.size(24.dp) + ) + } + } +} + +@Composable +fun WeeklyProgressSection() { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = "Weekly Progress", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + TextButton(onClick = { /* TODO */ }) { + Text("See History") + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + Card( + modifier = Modifier + .fillMaxWidth() + .height(200.dp), // Fixed height for dummy chart area + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.Bottom + ) { + // Dummy Chart Graph Space + Spacer(modifier = Modifier.weight(1f)) + + // Days row + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun").forEach { day -> + Text( + text = day, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) + ) + } + } + } + } + } +} + +@Composable +fun BottomStatsSection() { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Total Words + Card( + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column(modifier = Modifier.padding(20.dp)) { + Text(text = "TOTAL WORDS", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)) + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "1,284", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(8.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.TrendingUp, contentDescription = null, tint = Color.Green, modifier = Modifier.size(16.dp)) + Spacer(modifier = Modifier.width(4.dp)) + Text(text = "+12 today", style = MaterialTheme.typography.labelSmall, color = Color.Green) + } + } + } + + // Accuracy + Card( + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) + ) { + Column(modifier = Modifier.padding(20.dp)) { + Text(text = "ACCURACY", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)) + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "92%", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(8.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.CheckCircle, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(16.dp)) + Spacer(modifier = Modifier.width(4.dp)) + Text(text = "Master level", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.primary) + } + } + } + } +} \ 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 999655a..524af30 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -288,6 +288,7 @@ Hard First Row is a Header Hide examples + Home Import Import Table (CSV) In Stages