implement HomeScreen and refactor navigation to include a separate Home and Translation section

This commit is contained in:
jonasgaudian
2026-02-16 12:48:52 +01:00
parent 801b6f6404
commit 7fccda7f77
6 changed files with 382 additions and 63 deletions

View File

@@ -32,17 +32,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".CorrectActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</application> </application>

View File

@@ -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))
}
}
}
}

View File

@@ -28,6 +28,7 @@ import eu.gaudian.translator.view.exercises.ExerciseSessionScreen
import eu.gaudian.translator.view.exercises.MainExerciseScreen import eu.gaudian.translator.view.exercises.MainExerciseScreen
import eu.gaudian.translator.view.exercises.YouTubeBrowserScreen import eu.gaudian.translator.view.exercises.YouTubeBrowserScreen
import eu.gaudian.translator.view.exercises.YouTubeExerciseScreen 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.DictionaryOptionsScreen
import eu.gaudian.translator.view.settings.SettingsRoutes import eu.gaudian.translator.view.settings.SettingsRoutes
import eu.gaudian.translator.view.settings.TranslationSettingsScreen 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) // 1. Define your Main Tab "Leaf" Routes (the actual start destinations of your graphs)
val mainTabRoutes = setOf( val mainTabRoutes = setOf(
Screen.Home.route, // "home" or "main_translation" depending on which one you actually navigate to Screen.Home.route,
"main_translation", Screen.Translation.route,
"main_dictionary", "main_dictionary",
"main_vocabulary", "main_vocabulary",
"main_exercise", "main_exercise",
@@ -121,10 +122,11 @@ fun AppNavHost(
} }
) { ) {
composable(Screen.Home.route) { composable(Screen.Home.route) {
TranslationScreen(navController = navController) HomeScreen(navController = navController)
} }
// Define all other navigation graphs at the same top level. // Define all other navigation graphs at the same top level.
homeGraph(navController)
translationGraph(navController) translationGraph(navController)
dictionaryGraph(navController) dictionaryGraph(navController)
vocabularyGraph(navController) vocabularyGraph(navController)
@@ -132,11 +134,23 @@ fun AppNavHost(
settingsGraph(navController) 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) @OptIn(androidx.compose.animation.ExperimentalAnimationApi::class)
fun NavGraphBuilder.translationGraph(navController: NavHostController) { fun NavGraphBuilder.translationGraph(navController: NavHostController) {
navigation( navigation(
startDestination = "main_translation", startDestination = "main_translation",
route = Screen.Home.route route = Screen.Translation.route
) { ) {
composable("main_translation") { composable("main_translation") {
TranslationScreen(navController = navController) TranslationScreen(navController = navController)

View File

@@ -48,7 +48,8 @@ sealed class Screen(
val selectedIcon: ImageVector, val selectedIcon: ImageVector,
val unselectedIcon: 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 Dictionary : Screen("dictionary", R.string.label_dictionary, AppIcons.DictionaryFilled, AppIcons.DictionaryOutlined)
object Exercises : Screen("exercises", R.string.label_exercises, 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) object Vocabulary : Screen("vocabulary", R.string.label_vocabulary, AppIcons.VocabularyFilled, AppIcons.VocabularyOutlined)
@@ -56,9 +57,9 @@ sealed class Screen(
companion object { companion object {
fun getAllScreens(showExperimental: Boolean = false): List<Screen> { fun getAllScreens(showExperimental: Boolean = false): List<Screen> {
val screens = mutableListOf(Home, Dictionary, Vocabulary, Settings) val screens = mutableListOf(Home, Translation, Dictionary, Vocabulary, Settings)
if (showExperimental) { if (showExperimental) {
screens.add(2, Exercises) screens.add(3, Exercises)
} }
return screens return screens
} }

View File

@@ -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)
}
}
}
}
}

View File

@@ -288,6 +288,7 @@
<string name="label_hard">Hard</string> <string name="label_hard">Hard</string>
<string name="label_header_row">First Row is a Header</string> <string name="label_header_row">First Row is a Header</string>
<string name="label_hide_examples">Hide examples</string> <string name="label_hide_examples">Hide examples</string>
<string name="label_home">Home</string>
<string name="label_import">Import</string> <string name="label_import">Import</string>
<string name="label_import_table_csv_excel">Import Table (CSV)</string> <string name="label_import_table_csv_excel">Import Table (CSV)</string>
<string name="label_in_stages">In Stages</string> <string name="label_in_stages">In Stages</string>