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 49f5837..aaeed76 100644 --- a/app/src/main/java/eu/gaudian/translator/view/Navigation.kt +++ b/app/src/main/java/eu/gaudian/translator/view/Navigation.kt @@ -30,6 +30,7 @@ import eu.gaudian.translator.view.exercises.StartExerciseScreen 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.library.LibraryScreen import eu.gaudian.translator.view.settings.DictionaryOptionsScreen import eu.gaudian.translator.view.settings.SettingsRoutes import eu.gaudian.translator.view.settings.TranslationSettingsScreen @@ -61,6 +62,7 @@ fun AppNavHost( // 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, Screen.Translation.route, Screen.Vocabulary.route, @@ -132,6 +134,10 @@ fun AppNavHost( StatsScreen(navController = navController) } + composable(Screen.Library.route) { + LibraryScreen(navController = navController) + } + composable("start_exercise") { StartExerciseScreen(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 e1670fa..f13b3c7 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 @@ -73,7 +73,8 @@ sealed class Screen( object Home : Screen("home", R.string.label_home, AppIcons.Home, AppIcons.Home) object Stats : Screen("stats", R.string.label_stats, AppIcons.Statistics, AppIcons.Statistics) object Translation : Screen("translation", R.string.label_translation, AppIcons.TranslateFilled, AppIcons.TranslateOutlined) - object Vocabulary : Screen("vocabulary", R.string.label_vocabulary, AppIcons.VocabularyFilled, AppIcons.VocabularyOutlined) + object Library : Screen("library", R.string.label_library, AppIcons.VocabularyFilled, AppIcons.VocabularyOutlined) + object Vocabulary : Screen("vocabulary", R.string.label_legacy_vocabulary, AppIcons.VocabularyFilled, AppIcons.VocabularyOutlined) object Settings : Screen("settings", R.string.title_settings, AppIcons.SettingsFilled, AppIcons.SettingsOutlined) object More : Screen("more", R.string.label_more, AppIcons.MoreVert, AppIcons.MoreVert) object Dictionary : Screen("dictionary", R.string.label_dictionary, AppIcons.DictionaryFilled, AppIcons.DictionaryOutlined) @@ -81,12 +82,13 @@ sealed class Screen( companion object { fun getAllScreens(showExperimental: Boolean = false): List { - return listOf(Home, Vocabulary, Stats) + return listOf(Home, Library, Stats) } fun getMoreMenuItems(showExperimental: Boolean = false): List { val items = mutableListOf() items.add(Translation) + items.add(Vocabulary) // Legacy vocabulary moved to More items.add(Dictionary) items.add(Settings) if (showExperimental) { diff --git a/app/src/main/java/eu/gaudian/translator/view/exercises/StartExerciseScreen.kt b/app/src/main/java/eu/gaudian/translator/view/exercises/StartExerciseScreen.kt index 3947b95..f7b14ef 100644 --- a/app/src/main/java/eu/gaudian/translator/view/exercises/StartExerciseScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/exercises/StartExerciseScreen.kt @@ -1,12 +1,56 @@ package eu.gaudian.translator.view.exercises +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight 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.ArrowBackIosNew +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Hearing +import androidx.compose.material.icons.filled.List +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.outlined.Circle +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue 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.compose.ui.unit.sp import androidx.navigation.NavHostController @Composable @@ -16,11 +60,373 @@ fun StartExerciseScreen( ) { Box( modifier = modifier.fillMaxSize(), - contentAlignment = Alignment.Center + contentAlignment = Alignment.TopCenter + ) { + Column( + modifier = Modifier + .widthIn(max = 700.dp) // Keeps it from over-stretching on tablets + .fillMaxSize() + ) { + TopBarSection(onBackClick = { navController.popBackStack() }) + + LazyColumn( + modifier = Modifier + .weight(1f) + .padding(horizontal = 24.dp), + verticalArrangement = Arrangement.spacedBy(32.dp) + ) { + item { Spacer(modifier = Modifier.height(8.dp)) } + item { LanguagePairSection() } + item { CategoriesSection() } + item { DifficultySection() } + item { NumberOfCardsSection() } + item { QuestionTypesSection() } + item { Spacer(modifier = Modifier.height(24.dp)) } + } + + BottomButtonSection() + } + } +} + +@Composable +fun TopBarSection(onBackClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = onBackClick, + modifier = Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) + ) { + Icon( + imageVector = Icons.Default.ArrowBackIosNew, + contentDescription = "Back", + modifier = Modifier.size(18.dp), + tint = MaterialTheme.colorScheme.primary + ) + } + + Text( + text = "Start Exercise", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f), + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + + // Spacer to balance the back button for centering + Spacer(modifier = Modifier.size(48.dp)) + } +} + +@Composable +fun SectionHeader(title: String, actionText: String? = null, onActionClick: () -> Unit = {}) { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Start Exercise Screen", - style = MaterialTheme.typography.headlineMedium + text = title.uppercase(), + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold, + letterSpacing = 1.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + if (actionText != null) { + Text( + text = actionText, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold, + modifier = Modifier.clickable { onActionClick() } + ) + } + } +} + +@Composable +fun LanguagePairSection() { + var selectedPair by remember { mutableStateOf(0) } + + Column { + SectionHeader(title = "Language Pair", actionText = "Change") + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + LanguageChip( + text = "EN → ES", + isSelected = selectedPair == 0, + modifier = Modifier.weight(1f), + onClick = { selectedPair = 0 } + ) + LanguageChip( + text = "EN → FR", + isSelected = selectedPair == 1, + modifier = Modifier.weight(1f), + onClick = { selectedPair = 1 } + ) + } + } +} + +@Composable +fun LanguageChip(text: String, isSelected: Boolean, modifier: Modifier = Modifier, onClick: () -> Unit) { + Surface( + modifier = modifier.height(56.dp), + shape = RoundedCornerShape(16.dp), + color = if (isSelected) MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) else MaterialTheme.colorScheme.surfaceVariant, + border = if (isSelected) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null, + onClick = onClick + ) { + Row( + modifier = Modifier.padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + // Dummy overlapping flags + Box(modifier = Modifier.width(32.dp)) { + Box(modifier = Modifier.size(20.dp).clip(CircleShape).background(Color.Red).align(Alignment.CenterStart)) + Box(modifier = Modifier.size(20.dp).clip(CircleShape).background(Color.Blue).align(Alignment.CenterEnd)) + } + Spacer(modifier = Modifier.width(8.dp)) + Text(text = text, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium) + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun CategoriesSection() { + val categories = listOf("Travel", "Business", "Food", "Technology", "Slang", "Academic", "Relationships") + var selectedCategories by remember { mutableStateOf(setOf("Travel", "Food")) } + + Column { + SectionHeader(title = "Categories") + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxWidth() + ) { + categories.forEach { category -> + val isSelected = selectedCategories.contains(category) + Surface( + shape = RoundedCornerShape(20.dp), + color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant, + onClick = { + selectedCategories = if (isSelected) selectedCategories - category else selectedCategories + category + } + ) { + Text( + text = category, + color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + style = MaterialTheme.typography.bodyMedium, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal + ) + } + } + } + } +} + +@Composable +fun DifficultySection() { + val difficulties = listOf("Easy", "Medium", "Hard") + var selectedDifficulty by remember { mutableStateOf("Medium") } + + Column { + SectionHeader(title = "Difficulty Level") + Surface( + shape = RoundedCornerShape(50), + color = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier.fillMaxWidth().height(56.dp) + ) { + Row( + modifier = Modifier.fillMaxSize().padding(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + difficulties.forEach { level -> + val isSelected = selectedDifficulty == level + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .clip(RoundedCornerShape(50)) + .background(if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent) + .clickable { selectedDifficulty = level }, + contentAlignment = Alignment.Center + ) { + Text( + text = level, + color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } + } +} + +@Composable +fun NumberOfCardsSection() { + var sliderPosition by remember { mutableFloatStateOf(25f) } + + Column { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "NUMBER OF CARDS", + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold, + letterSpacing = 1.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Surface( + shape = RoundedCornerShape(12.dp), + color = MaterialTheme.colorScheme.primary, + ) { + Text( + text = sliderPosition.toInt().toString(), + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + ) + } + } + + Slider( + value = sliderPosition, + onValueChange = { sliderPosition = it }, + valueRange = 5f..50f, + steps = 45 + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text("5 CARDS", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) + Text("50 CARDS", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + } +} + +@Composable +fun QuestionTypesSection() { + var selectedTypes by remember { mutableStateOf(setOf("Multiple Choice", "Spelling")) } + + Column { + SectionHeader(title = "Question Types") + + QuestionTypeCard( + title = "Multiple Choice", + subtitle = "Choose the correct meaning", + icon = Icons.Default.List, + isSelected = selectedTypes.contains("Multiple Choice"), + onClick = { + selectedTypes = if (selectedTypes.contains("Multiple Choice")) selectedTypes - "Multiple Choice" else selectedTypes + "Multiple Choice" + } + ) + Spacer(modifier = Modifier.height(12.dp)) + QuestionTypeCard( + title = "Spelling", + subtitle = "Type the translated word", + icon = Icons.Default.Edit, + isSelected = selectedTypes.contains("Spelling"), + onClick = { + selectedTypes = if (selectedTypes.contains("Spelling")) selectedTypes - "Spelling" else selectedTypes + "Spelling" + } + ) + Spacer(modifier = Modifier.height(12.dp)) + QuestionTypeCard( + title = "Listening", + subtitle = "Recognize spoken words", + icon = Icons.Default.Hearing, + isSelected = selectedTypes.contains("Listening"), + onClick = { + selectedTypes = if (selectedTypes.contains("Listening")) selectedTypes - "Listening" else selectedTypes + "Listening" + } ) } } + +@Composable +fun QuestionTypeCard(title: String, subtitle: String, icon: ImageVector, isSelected: Boolean, onClick: () -> Unit) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = if (isSelected) MaterialTheme.colorScheme.primary.copy(alpha = 0.05f) else MaterialTheme.colorScheme.surfaceVariant, + border = if (isSelected) BorderStroke(1.dp, MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)) else null, + onClick = onClick + ) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surface), + contentAlignment = Alignment.Center + ) { + Icon(imageVector = icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary) + } + Spacer(modifier = Modifier.width(16.dp)) + Column(modifier = Modifier.weight(1f)) { + Text(text = title, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge) + Text(text = subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant) + } + Icon( + imageVector = if (isSelected) Icons.Default.CheckCircle else Icons.Outlined.Circle, + contentDescription = null, + tint = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +@Composable +fun BottomButtonSection() { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp) + ) { + Button( + onClick = { /* TODO: Start Session */ }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(28.dp), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary) + ) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Start Session", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.width(8.dp)) + Icon( + imageVector = Icons.Default.PlayArrow, + contentDescription = "Play", + modifier = Modifier.size(20.dp) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt b/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt new file mode 100644 index 0000000..03f15e7 --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt @@ -0,0 +1,26 @@ +package eu.gaudian.translator.view.library + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController + +@Composable +fun LibraryScreen( + navController: NavHostController, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = "Library Screen", + style = MaterialTheme.typography.headlineMedium + ) + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f68f35..a13f26e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1116,4 +1116,6 @@ This is a test success message! Oops, something went wrong :( Stats + Library + Legacy Vocabulary