Add dummy start exercise button and dummy screen
This commit is contained in:
@@ -285,6 +285,16 @@ fun TranslatorApp(
|
||||
restoreState = false
|
||||
}
|
||||
}
|
||||
},
|
||||
onPlayClicked = {
|
||||
navController.navigate("start_exercise") {
|
||||
popUpTo(0) {
|
||||
inclusive = true
|
||||
saveState = false
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = false
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
@@ -26,6 +26,7 @@ import eu.gaudian.translator.view.dictionary.EtymologyResultScreen
|
||||
import eu.gaudian.translator.view.dictionary.MainDictionaryScreen
|
||||
import eu.gaudian.translator.view.exercises.ExerciseSessionScreen
|
||||
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.YouTubeExerciseScreen
|
||||
import eu.gaudian.translator.view.home.HomeScreen
|
||||
@@ -131,6 +132,10 @@ fun AppNavHost(
|
||||
StatsScreen(navController = navController)
|
||||
}
|
||||
|
||||
composable("start_exercise") {
|
||||
StartExerciseScreen(navController = navController)
|
||||
}
|
||||
|
||||
// Define all other navigation graphs at the same top level.
|
||||
homeGraph(navController)
|
||||
translationGraph(navController)
|
||||
|
||||
@@ -21,8 +21,13 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -40,7 +45,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.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.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
@@ -106,6 +114,7 @@ fun BottomNavigationBar(
|
||||
showLabels: Boolean,
|
||||
onItemSelected: (Screen) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onPlayClicked: () -> Unit = {}
|
||||
) {
|
||||
val showExperimental = LocalShowExperimentalFeatures.current
|
||||
val screens = remember(showExperimental) { Screen.getAllScreens(showExperimental) }
|
||||
@@ -115,14 +124,22 @@ fun BottomNavigationBar(
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
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(
|
||||
visible = isVisible,
|
||||
enter = slideInVertically(
|
||||
animationSpec = androidx.compose.animation.core.tween(durationMillis = 220),
|
||||
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
||||
initialOffsetY = { it }
|
||||
),
|
||||
exit = slideOutVertically(
|
||||
animationSpec = androidx.compose.animation.core.tween(durationMillis = 220),
|
||||
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
|
||||
targetOffsetY = { it }
|
||||
)
|
||||
) {
|
||||
@@ -131,109 +148,146 @@ fun BottomNavigationBar(
|
||||
val navBarDp = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() }
|
||||
val height = baseHeight + navBarDp
|
||||
|
||||
NavigationBar(
|
||||
modifier = modifier.height(height),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
tonalElevation = 8.dp,
|
||||
// Outer Box height is purely determined by the NavigationBar now
|
||||
Box(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.TopCenter
|
||||
) {
|
||||
screens.forEach { screen ->
|
||||
val isSelected = screen == selectedItem
|
||||
val title = stringResource(id = screen.title)
|
||||
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isSelected) 1.2f else 1.0f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
),
|
||||
label = "iconScale"
|
||||
)
|
||||
// The actual Navigation Bar
|
||||
NavigationBar(
|
||||
modifier = Modifier.height(height),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
tonalElevation = 8.dp,
|
||||
) {
|
||||
// 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(
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
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
|
||||
allNavItems.forEach { screen ->
|
||||
if (screen == null) {
|
||||
// Dummy item to create the gap
|
||||
NavigationBarItem(
|
||||
selected = false,
|
||||
onClick = {},
|
||||
enabled = false, // Disables ripples and clicks
|
||||
icon = { Spacer(modifier = Modifier.size(24.dp)) },
|
||||
label = if (showLabels) { { Spacer(modifier = Modifier.size(10.dp)) } } else null,
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
disabledIconColor = Color.Transparent,
|
||||
disabledTextColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
} 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,
|
||||
icon = {
|
||||
Crossfade(targetState = isSelected, label = "iconFade") { selected ->
|
||||
Icon(
|
||||
imageVector = if (selected) screen.selectedIcon else screen.unselectedIcon,
|
||||
contentDescription = title,
|
||||
modifier = Modifier.scale(scale)
|
||||
val title = stringResource(id = screen.title)
|
||||
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (isSelected) 1.2f else 1.0f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||
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,
|
||||
contentDescription = moreTitle,
|
||||
modifier = Modifier.scale(moreScale)
|
||||
)
|
||||
},
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
indicatorColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
selectedTextColor = MaterialTheme.colorScheme.primary,
|
||||
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
// The Glowing Play Button
|
||||
Box(
|
||||
modifier = Modifier
|
||||
// This negative offset pulls the button UP out of the bounding box
|
||||
// without increasing the layout height of the parent Box.
|
||||
.offset(y = -upwardOffset)
|
||||
.size(playButtonSize + glowPadding),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
// Background radial glow
|
||||
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) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { showMoreMenu = false },
|
||||
@@ -325,4 +379,4 @@ fun BottomNavigationBarPreview() {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user