Add dummy start exercise button and dummy screen
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
@@ -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