feat(ui): use toolbar instead of navigation bar for navigation

Closes: #111
This commit is contained in:
Nishant Mishra
2025-11-30 11:30:56 +05:30
parent 1a52bf53b9
commit 0dcaddf4fe
7 changed files with 682 additions and 525 deletions

View File

@@ -21,36 +21,56 @@ import android.content.Intent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingToolbarDefaults
import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
import androidx.compose.material3.FloatingToolbarExitDirection
import androidx.compose.material3.HorizontalFloatingToolbar
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.MaterialTheme.motionScheme
import androidx.compose.material3.NavigationItemIconPosition
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.ShortNavigationBar
import androidx.compose.material3.ShortNavigationBarArrangement
import androidx.compose.material3.ShortNavigationBarItem
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.ToggleButton
import androidx.compose.material3.ToggleButtonDefaults
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.entryProvider
@@ -82,8 +102,13 @@ fun AppScreen(
val layoutDirection = LocalLayoutDirection.current val layoutDirection = LocalLayoutDirection.current
val motionScheme = motionScheme val motionScheme = motionScheme
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val systemBarsInsets = WindowInsets.systemBars.asPaddingValues()
val cutoutInsets = WindowInsets.displayCutout.asPaddingValues()
val backStack = rememberNavBackStack(Screen.Timer) val backStack = rememberNavBackStack(Screen.Timer)
val toolbarScrollBehavior = FloatingToolbarDefaults.exitAlwaysScrollBehavior(
FloatingToolbarExitDirection.Bottom
)
if (uiState.alarmRinging) if (uiState.alarmRinging)
AlarmDialog { AlarmDialog {
@@ -99,46 +124,92 @@ fun AppScreen(
bottomBar = { bottomBar = {
AnimatedVisibility( AnimatedVisibility(
backStack.last() !is Screen.AOD, backStack.last() !is Screen.AOD,
enter = fadeIn(), enter = slideInVertically(motionScheme.slowSpatialSpec()) { it },
exit = fadeOut() exit = slideOutVertically(motionScheme.slowSpatialSpec()) { it }
) { ) {
val wide = remember { val wide = remember {
windowSizeClass.isWidthAtLeastBreakpoint( windowSizeClass.isWidthAtLeastBreakpoint(
WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND
) )
} }
ShortNavigationBar( Box(
arrangement = Modifier
if (wide) ShortNavigationBarArrangement.Centered .fillMaxWidth()
else ShortNavigationBarArrangement.EqualWeight .padding(
start = cutoutInsets.calculateStartPadding(layoutDirection),
end = cutoutInsets.calculateEndPadding(layoutDirection)
),
Alignment.Center
) { ) {
mainScreens.forEach { HorizontalFloatingToolbar(
val selected = backStack.last() == it.route expanded = true,
ShortNavigationBarItem( scrollBehavior = toolbarScrollBehavior,
selected = selected, colors = FloatingToolbarDefaults.vibrantFloatingToolbarColors(
onClick = if (it.route != Screen.Timer) { // Ensure the backstack does not accumulate screens toolbarContainerColor = colorScheme.primary,
toolbarContentColor = colorScheme.onPrimary
),
modifier = Modifier
.padding(
top = ScreenOffset,
bottom = systemBarsInsets.calculateBottomPadding()
+ ScreenOffset
)
.zIndex(1f)
) {
mainScreens.forEach { item ->
val selected = backStack.last() == item.route
ToggleButton(
checked = selected,
onCheckedChange = if (item.route != Screen.Timer) { // Ensure the backstack does not accumulate screens
{ {
if (backStack.size < 2) backStack.add(it.route) if (backStack.size < 2) backStack.add(item.route)
else backStack[1] = it.route else backStack[1] = item.route
} }
} else { } else {
{ if (backStack.size > 1) backStack.removeAt(1) } { if (backStack.size > 1) backStack.removeAt(1) }
}, },
icon = { colors = ToggleButtonDefaults.toggleButtonColors(
containerColor = colorScheme.primary,
contentColor = colorScheme.onPrimary,
checkedContainerColor = colorScheme.primaryContainer,
checkedContentColor = colorScheme.onPrimaryContainer
),
shapes = ToggleButtonDefaults.shapes(
CircleShape,
CircleShape,
CircleShape
),
modifier = Modifier.height(56.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Crossfade(selected) { selected -> Crossfade(selected) { selected ->
if (selected) Icon(painterResource(it.selectedIcon), null) if (selected) Icon(painterResource(item.selectedIcon), null)
else Icon(painterResource(it.unselectedIcon), null) else Icon(painterResource(item.unselectedIcon), null)
}
AnimatedVisibility(
selected || wide,
enter = fadeIn(motionScheme.defaultEffectsSpec()) + expandHorizontally(
motionScheme.defaultSpatialSpec()
),
exit = fadeOut(motionScheme.defaultEffectsSpec()) + shrinkHorizontally(
motionScheme.defaultSpatialSpec()
),
) {
Row {
Spacer(Modifier.width(ButtonDefaults.MediumIconSpacing))
Text(stringResource(item.label))
}
}
}
}
}
}
}
} }
}, },
iconPosition = modifier = modifier
if (wide) NavigationItemIconPosition.Start
else NavigationItemIconPosition.Top,
label = { Text(stringResource(it.label)) }
)
}
}
}
}
) { contentPadding -> ) { contentPadding ->
SharedTransitionLayout { SharedTransitionLayout {
NavDisplay( NavDisplay(
@@ -161,20 +232,12 @@ fun AppScreen(
TimerScreen( TimerScreen(
timerState = uiState, timerState = uiState,
isPlus = isPlus, isPlus = isPlus,
contentPadding = contentPadding,
progress = { progress }, progress = { progress },
onAction = timerViewModel::onAction, onAction = timerViewModel::onAction,
modifier = modifier modifier = if (isAODEnabled) Modifier.clickable {
.padding(
start = contentPadding.calculateStartPadding(layoutDirection),
end = contentPadding.calculateEndPadding(layoutDirection),
bottom = contentPadding.calculateBottomPadding()
)
.then(
if (isAODEnabled) Modifier.clickable {
if (backStack.size < 2) backStack.add(Screen.AOD) if (backStack.size < 2) backStack.add(Screen.AOD)
} } else Modifier
else Modifier
),
) )
} }
@@ -183,36 +246,21 @@ fun AppScreen(
timerState = uiState, timerState = uiState,
progress = { progress }, progress = { progress },
setTimerFrequency = setTimerFrequency, setTimerFrequency = setTimerFrequency,
modifier = Modifier modifier = if (isAODEnabled) Modifier.clickable {
.then(
if (isAODEnabled) Modifier.clickable {
if (backStack.size > 1) backStack.removeLastOrNull() if (backStack.size > 1) backStack.removeLastOrNull()
} } else Modifier
else Modifier
)
) )
} }
entry<Screen.Settings.Main> { entry<Screen.Settings.Main> {
SettingsScreenRoot( SettingsScreenRoot(
setShowPaywall = { showPaywall = it }, setShowPaywall = { showPaywall = it },
modifier = modifier.padding( contentPadding = contentPadding
start = contentPadding.calculateStartPadding(layoutDirection),
end = contentPadding.calculateEndPadding(layoutDirection),
bottom = contentPadding.calculateBottomPadding()
)
) )
} }
entry<Screen.Stats> { entry<Screen.Stats> {
StatsScreenRoot( StatsScreenRoot(contentPadding = contentPadding)
contentPadding = contentPadding,
modifier = modifier.padding(
start = contentPadding.calculateStartPadding(layoutDirection),
end = contentPadding.calculateEndPadding(layoutDirection),
bottom = contentPadding.calculateBottomPadding()
)
)
} }
} }
) )

View File

@@ -27,8 +27,10 @@ import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -39,6 +41,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SliderState import androidx.compose.material3.SliderState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@@ -55,6 +58,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -86,6 +90,7 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors
@Composable @Composable
fun SettingsScreenRoot( fun SettingsScreenRoot(
setShowPaywall: (Boolean) -> Unit, setShowPaywall: (Boolean) -> Unit,
contentPadding: PaddingValues,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory) viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory)
) { ) {
@@ -119,6 +124,7 @@ fun SettingsScreenRoot(
serviceRunning = serviceRunning, serviceRunning = serviceRunning,
settingsState = settingsState, settingsState = settingsState,
backStack = backStack, backStack = backStack,
contentPadding = contentPadding,
focusTimeInputFieldState = focusTimeInputFieldState, focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState,
@@ -137,6 +143,7 @@ private fun SettingsScreen(
serviceRunning: Boolean, serviceRunning: Boolean,
settingsState: SettingsState, settingsState: SettingsState,
backStack: SnapshotStateList<Screen.Settings>, backStack: SnapshotStateList<Screen.Settings>,
contentPadding: PaddingValues,
focusTimeInputFieldState: TextFieldState, focusTimeInputFieldState: TextFieldState,
shortBreakTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState,
longBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState,
@@ -146,6 +153,7 @@ private fun SettingsScreen(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val context = LocalContext.current val context = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
val currentLocales = val currentLocales =
@@ -181,7 +189,8 @@ private fun SettingsScreen(
}, },
entryProvider = entryProvider { entryProvider = entryProvider {
entry<Screen.Settings.Main> { entry<Screen.Settings.Main> {
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { Scaffold(
topBar = {
TopAppBar( TopAppBar(
title = { title = {
Text( Text(
@@ -198,9 +207,18 @@ private fun SettingsScreen(
titleHorizontalAlignment = Alignment.CenterHorizontally, titleHorizontalAlignment = Alignment.CenterHorizontally,
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
},
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
val insets = PaddingValues(
bottom = contentPadding.calculateBottomPadding(),
top = innerPadding.calculateTopPadding(),
start = innerPadding.calculateStartPadding(layoutDirection),
end = innerPadding.calculateEndPadding(layoutDirection)
)
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp),
contentPadding = insets,
modifier = Modifier modifier = Modifier
.background(topBarColors.containerColor) .background(topBarColors.containerColor)
.fillMaxSize() .fillMaxSize()
@@ -279,6 +297,7 @@ private fun SettingsScreen(
entry<Screen.Settings.Alarm> { entry<Screen.Settings.Alarm> {
AlarmSettings( AlarmSettings(
settingsState = settingsState, settingsState = settingsState,
contentPadding = contentPadding,
onAction = onAction, onAction = onAction,
onBack = backStack::removeLastOrNull, onBack = backStack::removeLastOrNull,
modifier = modifier, modifier = modifier,
@@ -287,6 +306,7 @@ private fun SettingsScreen(
entry<Screen.Settings.Appearance> { entry<Screen.Settings.Appearance> {
AppearanceSettings( AppearanceSettings(
settingsState = settingsState, settingsState = settingsState,
contentPadding = contentPadding,
isPlus = isPlus, isPlus = isPlus,
onAction = onAction, onAction = onAction,
setShowPaywall = setShowPaywall, setShowPaywall = setShowPaywall,
@@ -299,6 +319,7 @@ private fun SettingsScreen(
isPlus = isPlus, isPlus = isPlus,
serviceRunning = serviceRunning, serviceRunning = serviceRunning,
settingsState = settingsState, settingsState = settingsState,
contentPadding = contentPadding,
focusTimeInputFieldState = focusTimeInputFieldState, focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState,

View File

@@ -28,8 +28,10 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -43,6 +45,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LargeFlexibleTopAppBar import androidx.compose.material3.LargeFlexibleTopAppBar
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -57,6 +60,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -80,12 +84,14 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
@Composable @Composable
fun AlarmSettings( fun AlarmSettings(
settingsState: SettingsState, settingsState: SettingsState,
contentPadding: PaddingValues,
onAction: (SettingsAction) -> Unit, onAction: (SettingsAction) -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
val context = LocalContext.current val context = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
var alarmName by remember { mutableStateOf("...") } var alarmName by remember { mutableStateOf("...") }
@@ -148,7 +154,8 @@ fun AlarmSettings(
) )
} }
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { Scaffold(
topBar = {
LargeFlexibleTopAppBar( LargeFlexibleTopAppBar(
title = { title = {
Text(stringResource(R.string.alarm), fontFamily = robotoFlexTopBar) Text(stringResource(R.string.alarm), fontFamily = robotoFlexTopBar)
@@ -171,9 +178,18 @@ fun AlarmSettings(
colors = topBarColors, colors = topBarColors,
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
},
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
val insets = PaddingValues(
bottom = contentPadding.calculateBottomPadding(),
top = innerPadding.calculateTopPadding(),
start = innerPadding.calculateStartPadding(layoutDirection),
end = innerPadding.calculateEndPadding(layoutDirection)
)
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp),
contentPadding = insets,
modifier = Modifier modifier = Modifier
.background(topBarColors.containerColor) .background(topBarColors.containerColor)
.fillMaxSize() .fillMaxSize()
@@ -248,6 +264,7 @@ fun AlarmSettingsPreview() {
val settingsState = SettingsState() val settingsState = SettingsState()
AlarmSettings( AlarmSettings(
settingsState = settingsState, settingsState = settingsState,
contentPadding = PaddingValues(),
onAction = {}, onAction = {},
onBack = {} onBack = {}
) )

View File

@@ -19,8 +19,10 @@ package org.nsh07.pomodoro.ui.settingsScreen.screens
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -33,6 +35,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LargeFlexibleTopAppBar import androidx.compose.material3.LargeFlexibleTopAppBar
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -41,6 +44,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -64,6 +68,7 @@ import org.nsh07.pomodoro.utils.toColor
@Composable @Composable
fun AppearanceSettings( fun AppearanceSettings(
settingsState: SettingsState, settingsState: SettingsState,
contentPadding: PaddingValues,
isPlus: Boolean, isPlus: Boolean,
onAction: (SettingsAction) -> Unit, onAction: (SettingsAction) -> Unit,
setShowPaywall: (Boolean) -> Unit, setShowPaywall: (Boolean) -> Unit,
@@ -71,8 +76,10 @@ fun AppearanceSettings(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
val layoutDirection = LocalLayoutDirection.current
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { Scaffold(
topBar = {
LargeFlexibleTopAppBar( LargeFlexibleTopAppBar(
title = { title = {
Text(stringResource(R.string.appearance), fontFamily = robotoFlexTopBar) Text(stringResource(R.string.appearance), fontFamily = robotoFlexTopBar)
@@ -95,9 +102,18 @@ fun AppearanceSettings(
colors = topBarColors, colors = topBarColors,
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
},
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
val insets = PaddingValues(
bottom = contentPadding.calculateBottomPadding(),
top = innerPadding.calculateTopPadding(),
start = innerPadding.calculateStartPadding(layoutDirection),
end = innerPadding.calculateEndPadding(layoutDirection)
)
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp),
contentPadding = insets,
modifier = Modifier modifier = Modifier
.background(topBarColors.containerColor) .background(topBarColors.containerColor)
.fillMaxSize() .fillMaxSize()
@@ -182,6 +198,7 @@ fun AppearanceSettingsPreview() {
TomatoTheme(dynamicColor = false) { TomatoTheme(dynamicColor = false) {
AppearanceSettings( AppearanceSettings(
settingsState = settingsState, settingsState = settingsState,
contentPadding = PaddingValues(),
isPlus = false, isPlus = false,
onAction = {}, onAction = {},
setShowPaywall = {}, setShowPaywall = {},

View File

@@ -27,8 +27,11 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize 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
@@ -53,6 +56,7 @@ import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.SliderState import androidx.compose.material3.SliderState
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
@@ -71,6 +75,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@@ -98,6 +103,7 @@ fun TimerSettings(
isPlus: Boolean, isPlus: Boolean,
serviceRunning: Boolean, serviceRunning: Boolean,
settingsState: SettingsState, settingsState: SettingsState,
contentPadding: PaddingValues,
focusTimeInputFieldState: TextFieldState, focusTimeInputFieldState: TextFieldState,
shortBreakTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState,
longBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState,
@@ -109,6 +115,7 @@ fun TimerSettings(
) { ) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
val context = LocalContext.current val context = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
val appName = stringResource(R.string.app_name) val appName = stringResource(R.string.app_name)
val notificationManagerService = val notificationManagerService =
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
@@ -141,7 +148,8 @@ fun TimerSettings(
) )
) )
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { Scaffold(
topBar = {
LargeFlexibleTopAppBar( LargeFlexibleTopAppBar(
title = { title = {
Text(stringResource(R.string.timer), fontFamily = robotoFlexTopBar) Text(stringResource(R.string.timer), fontFamily = robotoFlexTopBar)
@@ -164,9 +172,18 @@ fun TimerSettings(
colors = topBarColors, colors = topBarColors,
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
},
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
val insets = PaddingValues(
bottom = contentPadding.calculateBottomPadding(),
top = innerPadding.calculateTopPadding(),
start = innerPadding.calculateStartPadding(layoutDirection),
end = innerPadding.calculateEndPadding(layoutDirection)
)
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp),
contentPadding = insets,
modifier = Modifier modifier = Modifier
.background(topBarColors.containerColor) .background(topBarColors.containerColor)
.fillMaxSize() .fillMaxSize()
@@ -428,6 +445,7 @@ private fun TimerSettingsPreview() {
isPlus = false, isPlus = false,
serviceRunning = true, serviceRunning = true,
settingsState = remember { SettingsState() }, settingsState = remember { SettingsState() },
contentPadding = PaddingValues(),
focusTimeInputFieldState = focusTimeInputFieldState, focusTimeInputFieldState = focusTimeInputFieldState,
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
longBreakTimeInputFieldState = longBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState,

View File

@@ -27,6 +27,8 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
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.padding import androidx.compose.foundation.layout.padding
@@ -45,6 +47,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.MaterialTheme.motionScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@@ -61,6 +64,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalFontFamilyResolver import androidx.compose.ui.platform.LocalFontFamilyResolver
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -129,6 +133,7 @@ fun StatsScreen(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
val layoutDirection = LocalLayoutDirection.current
val hoursFormat = stringResource(R.string.hours_format) val hoursFormat = stringResource(R.string.hours_format)
val hoursMinutesFormat = stringResource(R.string.hours_and_minutes_format) val hoursMinutesFormat = stringResource(R.string.hours_and_minutes_format)
@@ -160,10 +165,8 @@ fun StatsScreen(
val axisTypeface = remember { resolver.resolve(googleFlex400).value as Typeface } val axisTypeface = remember { resolver.resolve(googleFlex400).value as Typeface }
val markerTypeface = remember { resolver.resolve(googleFlex600).value as Typeface } val markerTypeface = remember { resolver.resolve(googleFlex600).value as Typeface }
Column( Scaffold(
horizontalAlignment = Alignment.CenterHorizontally, topBar = {
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
TopAppBar( TopAppBar(
title = { title = {
Text( Text(
@@ -194,9 +197,18 @@ fun StatsScreen(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
windowInsets = WindowInsets() windowInsets = WindowInsets()
) )
},
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
val insets = PaddingValues(
bottom = contentPadding.calculateBottomPadding(),
top = innerPadding.calculateTopPadding(),
start = innerPadding.calculateStartPadding(layoutDirection),
end = innerPadding.calculateEndPadding(layoutDirection)
)
LazyColumn( LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
contentPadding = insets,
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
item { Spacer(Modifier) } item { Spacer(Modifier) }

View File

@@ -38,8 +38,11 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize 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
@@ -47,8 +50,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ButtonGroup import androidx.compose.material3.ButtonGroup
import androidx.compose.material3.ButtonGroupDefaults import androidx.compose.material3.ButtonGroupDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@@ -64,6 +66,7 @@ import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.MaterialTheme.motionScheme
import androidx.compose.material3.MaterialTheme.shapes import androidx.compose.material3.MaterialTheme.shapes
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@@ -84,6 +87,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -107,12 +111,14 @@ import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
fun SharedTransitionScope.TimerScreen( fun SharedTransitionScope.TimerScreen(
timerState: TimerState, timerState: TimerState,
isPlus: Boolean, isPlus: Boolean,
contentPadding: PaddingValues,
progress: () -> Float, progress: () -> Float,
onAction: (TimerAction) -> Unit, onAction: (TimerAction) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val motionScheme = motionScheme val motionScheme = motionScheme
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val layoutDirection = LocalLayoutDirection.current
val color by animateColorAsState( val color by animateColorAsState(
if (timerState.timerMode == TimerMode.FOCUS) colorScheme.primary if (timerState.timerMode == TimerMode.FOCUS) colorScheme.primary
@@ -137,7 +143,8 @@ fun SharedTransitionScope.TimerScreen(
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Column(modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { Scaffold(
topBar = {
TopAppBar( TopAppBar(
title = { title = {
AnimatedContent( AnimatedContent(
@@ -210,14 +217,24 @@ fun SharedTransitionScope.TimerScreen(
titleHorizontalAlignment = CenterHorizontally, titleHorizontalAlignment = CenterHorizontally,
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
},
Column( modifier = modifier
.nestedScroll(scrollBehavior.nestedScrollConnection)
) { innerPadding ->
val insets = PaddingValues(
bottom = contentPadding.calculateBottomPadding(),
top = innerPadding.calculateTopPadding(),
start = innerPadding.calculateStartPadding(layoutDirection),
end = innerPadding.calculateEndPadding(layoutDirection)
)
LazyColumn(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = CenterHorizontally, horizontalAlignment = CenterHorizontally,
contentPadding = insets,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState())
) { ) {
item {
Column(horizontalAlignment = CenterHorizontally) { Column(horizontalAlignment = CenterHorizontally) {
Box(contentAlignment = Alignment.Center) { Box(contentAlignment = Alignment.Center) {
if (timerState.timerMode == TimerMode.FOCUS) { if (timerState.timerMode == TimerMode.FOCUS) {
@@ -290,7 +307,9 @@ fun SharedTransitionScope.TimerScreen(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
maxLines = 1, maxLines = 1,
modifier = Modifier.sharedBounds( modifier = Modifier.sharedBounds(
sharedContentState = this@TimerScreen.rememberSharedContentState("clock"), sharedContentState = this@TimerScreen.rememberSharedContentState(
"clock"
),
animatedVisibilityScope = LocalNavAnimatedContentScope.current animatedVisibilityScope = LocalNavAnimatedContentScope.current
) )
) )
@@ -475,9 +494,11 @@ fun SharedTransitionScope.TimerScreen(
) )
} }
} }
}
Spacer(Modifier.height(32.dp)) item { Spacer(Modifier.height(32.dp)) }
item {
Column(horizontalAlignment = CenterHorizontally) { Column(horizontalAlignment = CenterHorizontally) {
Text(stringResource(R.string.up_next), style = typography.titleSmall) Text(stringResource(R.string.up_next), style = typography.titleSmall)
AnimatedContent( AnimatedContent(
@@ -532,12 +553,14 @@ fun SharedTransitionScope.TimerScreen(
) )
} }
} }
}
Spacer(Modifier.height(16.dp)) item { Spacer(Modifier.height(16.dp)) }
} }
} }
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview( @Preview(
showSystemUi = true, showSystemUi = true,
device = Devices.PIXEL_9_PRO device = Devices.PIXEL_9_PRO
@@ -553,6 +576,7 @@ fun TimerScreenPreview() {
TimerScreen( TimerScreen(
timerState, timerState,
isPlus = true, isPlus = true,
contentPadding = PaddingValues(),
{ 0.3f }, { 0.3f },
{} {}
) )