Add dummy start exercise button and dummy screen

This commit is contained in:
jonasgaudian
2026-02-16 13:52:02 +01:00
parent ef90df2150
commit 5ae96d1f5c
4 changed files with 189 additions and 94 deletions

View File

@@ -285,6 +285,16 @@ fun TranslatorApp(
restoreState = false restoreState = false
} }
} }
},
onPlayClicked = {
navController.navigate("start_exercise") {
popUpTo(0) {
inclusive = true
saveState = false
}
launchSingleTop = true
restoreState = false
}
} }
) )
}, },

View File

@@ -26,6 +26,7 @@ import eu.gaudian.translator.view.dictionary.EtymologyResultScreen
import eu.gaudian.translator.view.dictionary.MainDictionaryScreen import eu.gaudian.translator.view.dictionary.MainDictionaryScreen
import eu.gaudian.translator.view.exercises.ExerciseSessionScreen 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.StartExerciseScreen
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.home.HomeScreen
@@ -131,6 +132,10 @@ fun AppNavHost(
StatsScreen(navController = navController) StatsScreen(navController = navController)
} }
composable("start_exercise") {
StartExerciseScreen(navController = navController)
}
// Define all other navigation graphs at the same top level. // Define all other navigation graphs at the same top level.
homeGraph(navController) homeGraph(navController)
translationGraph(navController) translationGraph(navController)

View File

@@ -21,8 +21,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -40,7 +45,10 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
@@ -106,6 +114,7 @@ fun BottomNavigationBar(
showLabels: Boolean, showLabels: Boolean,
onItemSelected: (Screen) -> Unit, onItemSelected: (Screen) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onPlayClicked: () -> Unit = {}
) { ) {
val showExperimental = LocalShowExperimentalFeatures.current val showExperimental = LocalShowExperimentalFeatures.current
val screens = remember(showExperimental) { Screen.getAllScreens(showExperimental) } val screens = remember(showExperimental) { Screen.getAllScreens(showExperimental) }
@@ -115,14 +124,22 @@ fun BottomNavigationBar(
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
// Configuration for the play button
val playButtonSize = 56.dp
val glowPadding = 32.dp // Total extra space for the glow (16dp on each side)
// This dictates how far up the button shifts.
// Setting it to around half the button size centers it on the top border.
val upwardOffset = 28.dp
AnimatedVisibility( AnimatedVisibility(
visible = isVisible, visible = isVisible,
enter = slideInVertically( enter = slideInVertically(
animationSpec = androidx.compose.animation.core.tween(durationMillis = 220), animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
initialOffsetY = { it } initialOffsetY = { it }
), ),
exit = slideOutVertically( exit = slideOutVertically(
animationSpec = androidx.compose.animation.core.tween(durationMillis = 220), animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
targetOffsetY = { it } targetOffsetY = { it }
) )
) { ) {
@@ -131,109 +148,146 @@ fun BottomNavigationBar(
val navBarDp = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() } val navBarDp = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() }
val height = baseHeight + navBarDp val height = baseHeight + navBarDp
NavigationBar( // Outer Box height is purely determined by the NavigationBar now
modifier = modifier.height(height), Box(
containerColor = MaterialTheme.colorScheme.surface, modifier = modifier.fillMaxWidth(),
tonalElevation = 8.dp, contentAlignment = Alignment.TopCenter
) { ) {
screens.forEach { screen ->
val isSelected = screen == selectedItem
val title = stringResource(id = screen.title)
val scale by animateFloatAsState( // The actual Navigation Bar
targetValue = if (isSelected) 1.2f else 1.0f, NavigationBar(
animationSpec = spring( modifier = Modifier.height(height),
dampingRatio = Spring.DampingRatioMediumBouncy, containerColor = MaterialTheme.colorScheme.surface,
stiffness = Spring.StiffnessLow tonalElevation = 8.dp,
), ) {
label = "iconScale" // Create a list of 5 items (2 left, 1 empty spacer, 2 right)
) val allNavItems = buildList {
addAll(screens.take(2))
add(null) // Empty spacer for Play Button gap
if (screens.size > 2) {
addAll(screens.drop(2))
}
add(moreScreen)
}
NavigationBarItem( allNavItems.forEach { screen ->
selected = isSelected, if (screen == null) {
onClick = { // Dummy item to create the gap
haptic.performHapticFeedback(HapticFeedbackType.LongPress) NavigationBarItem(
onItemSelected(screen) selected = false,
}, onClick = {},
label = if (showLabels) { enabled = false, // Disables ripples and clicks
{ icon = { Spacer(modifier = Modifier.size(24.dp)) },
Text( label = if (showLabels) { { Spacer(modifier = Modifier.size(10.dp)) } } else null,
text = title, colors = NavigationBarItemDefaults.colors(
maxLines = 1, disabledIconColor = Color.Transparent,
fontSize = 10.sp, disabledTextColor = Color.Transparent
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
color = if(isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
) )
)
} else {
// Regular or More items
val isSelected = if (screen == Screen.More) {
selectedItem is Screen.More || Screen.getMoreMenuItems(showExperimental).contains(selectedItem)
} else {
screen == selectedItem
} }
} else null, val title = stringResource(id = screen.title)
icon = {
Crossfade(targetState = isSelected, label = "iconFade") { selected -> val scale by animateFloatAsState(
Icon( targetValue = if (isSelected) 1.2f else 1.0f,
imageVector = if (selected) screen.selectedIcon else screen.unselectedIcon, animationSpec = spring(
contentDescription = title, dampingRatio = Spring.DampingRatioMediumBouncy,
modifier = Modifier.scale(scale) stiffness = Spring.StiffnessLow
),
label = "iconScale"
)
NavigationBarItem(
selected = isSelected,
onClick = {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
if (screen == Screen.More) showMoreMenu = true else onItemSelected(screen)
},
label = if (showLabels) {
{
Text(
text = title,
maxLines = 1,
fontSize = 10.sp,
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
color = if(isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
)
}
} else null,
icon = {
Crossfade(targetState = isSelected, label = "iconFade") { selected ->
Icon(
imageVector = if (selected) screen.selectedIcon else screen.unselectedIcon,
contentDescription = title,
modifier = Modifier.scale(scale)
)
}
},
colors = NavigationBarItemDefaults.colors(
indicatorColor = MaterialTheme.colorScheme.primaryContainer,
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
selectedTextColor = MaterialTheme.colorScheme.primary,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
) )
}
},
colors = NavigationBarItemDefaults.colors(
indicatorColor = MaterialTheme.colorScheme.primaryContainer,
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
selectedTextColor = MaterialTheme.colorScheme.primary,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
)
)
}
// More menu item
val moreSelected = selectedItem is Screen.More ||
Screen.getMoreMenuItems(showExperimental).contains(selectedItem)
val moreTitle = stringResource(R.string.label_more)
val moreScale by animateFloatAsState(
targetValue = if (moreSelected) 1.2f else 1.0f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "moreIconScale"
)
NavigationBarItem(
selected = moreSelected,
onClick = {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
showMoreMenu = true
},
label = if (showLabels) {
{
Text(
text = moreTitle,
maxLines = 1,
fontSize = 10.sp,
fontWeight = if (moreSelected) FontWeight.Bold else FontWeight.Normal,
color = if(moreSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
} else null, }
icon = { }
Icon(
imageVector = moreScreen.selectedIcon, // The Glowing Play Button
contentDescription = moreTitle, Box(
modifier = Modifier.scale(moreScale) modifier = Modifier
) // This negative offset pulls the button UP out of the bounding box
}, // without increasing the layout height of the parent Box.
colors = NavigationBarItemDefaults.colors( .offset(y = -upwardOffset)
indicatorColor = MaterialTheme.colorScheme.primaryContainer, .size(playButtonSize + glowPadding),
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer, contentAlignment = Alignment.Center
selectedTextColor = MaterialTheme.colorScheme.primary, ) {
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant, // Background radial glow
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant Box(
modifier = Modifier
.matchParentSize()
.background(
brush = Brush.radialGradient(
colors = listOf(
Color(0xFF3B82F6).copy(alpha = 0.5f),
Color.Transparent
)
),
shape = CircleShape
)
) )
)
// Actual clickable button
Box(
modifier = Modifier
.size(playButtonSize)
.clip(CircleShape)
.background(Color(0xFF3B82F6))
.clickable {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
onPlayClicked()
},
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = "Play",
tint = Color.White,
modifier = Modifier.size(32.dp)
)
}
}
} }
} }
// Modal Bottom Sheet for More menu // Modal Bottom Sheet for More menu (Remains exactly the same)
if (showMoreMenu) { if (showMoreMenu) {
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { showMoreMenu = false }, onDismissRequest = { showMoreMenu = false },

View File

@@ -0,0 +1,26 @@
package eu.gaudian.translator.view.exercises
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 StartExerciseScreen(
navController: NavHostController,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "Start Exercise Screen",
style = MaterialTheme.typography.headlineMedium
)
}
}