diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt index a998da7..dd1005f 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -21,36 +21,56 @@ import android.content.Intent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.expandHorizontally import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkHorizontally import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith 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.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.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.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.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.motionScheme -import androidx.compose.material3.NavigationItemIconPosition 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.ToggleButton +import androidx.compose.material3.ToggleButtonDefaults import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource 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.viewmodel.compose.viewModel import androidx.navigation3.runtime.entryProvider @@ -82,8 +102,13 @@ fun AppScreen( val layoutDirection = LocalLayoutDirection.current val motionScheme = motionScheme val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val systemBarsInsets = WindowInsets.systemBars.asPaddingValues() + val cutoutInsets = WindowInsets.displayCutout.asPaddingValues() val backStack = rememberNavBackStack(Screen.Timer) + val toolbarScrollBehavior = FloatingToolbarDefaults.exitAlwaysScrollBehavior( + FloatingToolbarExitDirection.Bottom + ) if (uiState.alarmRinging) AlarmDialog { @@ -99,46 +124,92 @@ fun AppScreen( bottomBar = { AnimatedVisibility( backStack.last() !is Screen.AOD, - enter = fadeIn(), - exit = fadeOut() + enter = slideInVertically(motionScheme.slowSpatialSpec()) { it }, + exit = slideOutVertically(motionScheme.slowSpatialSpec()) { it } ) { val wide = remember { windowSizeClass.isWidthAtLeastBreakpoint( WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND ) } - ShortNavigationBar( - arrangement = - if (wide) ShortNavigationBarArrangement.Centered - else ShortNavigationBarArrangement.EqualWeight + Box( + Modifier + .fillMaxWidth() + .padding( + start = cutoutInsets.calculateStartPadding(layoutDirection), + end = cutoutInsets.calculateEndPadding(layoutDirection) + ), + Alignment.Center ) { - mainScreens.forEach { - val selected = backStack.last() == it.route - ShortNavigationBarItem( - selected = selected, - onClick = if (it.route != Screen.Timer) { // Ensure the backstack does not accumulate screens - { - if (backStack.size < 2) backStack.add(it.route) - else backStack[1] = it.route + HorizontalFloatingToolbar( + expanded = true, + scrollBehavior = toolbarScrollBehavior, + colors = FloatingToolbarDefaults.vibrantFloatingToolbarColors( + 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(item.route) + else backStack[1] = item.route + } + } else { + { if (backStack.size > 1) backStack.removeAt(1) } + }, + 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 -> + if (selected) Icon(painterResource(item.selectedIcon), 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)) + } + } } - } else { - { if (backStack.size > 1) backStack.removeAt(1) } - }, - icon = { - Crossfade(selected) { selected -> - if (selected) Icon(painterResource(it.selectedIcon), null) - else Icon(painterResource(it.unselectedIcon), null) - } - }, - iconPosition = - if (wide) NavigationItemIconPosition.Start - else NavigationItemIconPosition.Top, - label = { Text(stringResource(it.label)) } - ) + } + } } } } - } + }, + modifier = modifier ) { contentPadding -> SharedTransitionLayout { NavDisplay( @@ -161,20 +232,12 @@ fun AppScreen( TimerScreen( timerState = uiState, isPlus = isPlus, + contentPadding = contentPadding, progress = { progress }, onAction = timerViewModel::onAction, - modifier = modifier - .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) - } - else Modifier - ), + modifier = if (isAODEnabled) Modifier.clickable { + if (backStack.size < 2) backStack.add(Screen.AOD) + } else Modifier ) } @@ -183,36 +246,21 @@ fun AppScreen( timerState = uiState, progress = { progress }, setTimerFrequency = setTimerFrequency, - modifier = Modifier - .then( - if (isAODEnabled) Modifier.clickable { - if (backStack.size > 1) backStack.removeLastOrNull() - } - else Modifier - ) + modifier = if (isAODEnabled) Modifier.clickable { + if (backStack.size > 1) backStack.removeLastOrNull() + } else Modifier ) } entry { SettingsScreenRoot( setShowPaywall = { showPaywall = it }, - modifier = modifier.padding( - start = contentPadding.calculateStartPadding(layoutDirection), - end = contentPadding.calculateEndPadding(layoutDirection), - bottom = contentPadding.calculateBottomPadding() - ) + contentPadding = contentPadding ) } entry { - StatsScreenRoot( - contentPadding = contentPadding, - modifier = modifier.padding( - start = contentPadding.calculateStartPadding(layoutDirection), - end = contentPadding.calculateEndPadding(layoutDirection), - bottom = contentPadding.calculateBottomPadding() - ) - ) + StatsScreenRoot(contentPadding = contentPadding) } } ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt index 6c967ea..64b8b5f 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt @@ -27,8 +27,10 @@ import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.background 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.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -39,6 +41,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.Scaffold import androidx.compose.material3.SliderState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -55,6 +58,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -86,6 +90,7 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors @Composable fun SettingsScreenRoot( setShowPaywall: (Boolean) -> Unit, + contentPadding: PaddingValues, modifier: Modifier = Modifier, viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory) ) { @@ -119,6 +124,7 @@ fun SettingsScreenRoot( serviceRunning = serviceRunning, settingsState = settingsState, backStack = backStack, + contentPadding = contentPadding, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, @@ -137,6 +143,7 @@ private fun SettingsScreen( serviceRunning: Boolean, settingsState: SettingsState, backStack: SnapshotStateList, + contentPadding: PaddingValues, focusTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState, @@ -146,6 +153,7 @@ private fun SettingsScreen( modifier: Modifier = Modifier ) { val context = LocalContext.current + val layoutDirection = LocalLayoutDirection.current val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val currentLocales = @@ -181,26 +189,36 @@ private fun SettingsScreen( }, entryProvider = entryProvider { entry { - Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { - TopAppBar( - title = { - Text( - stringResource(R.string.settings), - style = LocalTextStyle.current.copy( - fontFamily = robotoFlexTopBar, - fontSize = 32.sp, - lineHeight = 32.sp + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + stringResource(R.string.settings), + style = LocalTextStyle.current.copy( + fontFamily = robotoFlexTopBar, + fontSize = 32.sp, + lineHeight = 32.sp + ) ) - ) - }, - subtitle = {}, - colors = topBarColors, - titleHorizontalAlignment = Alignment.CenterHorizontally, - scrollBehavior = scrollBehavior + }, + subtitle = {}, + colors = topBarColors, + titleHorizontalAlignment = Alignment.CenterHorizontally, + 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( verticalArrangement = Arrangement.spacedBy(2.dp), + contentPadding = insets, modifier = Modifier .background(topBarColors.containerColor) .fillMaxSize() @@ -279,6 +297,7 @@ private fun SettingsScreen( entry { AlarmSettings( settingsState = settingsState, + contentPadding = contentPadding, onAction = onAction, onBack = backStack::removeLastOrNull, modifier = modifier, @@ -287,6 +306,7 @@ private fun SettingsScreen( entry { AppearanceSettings( settingsState = settingsState, + contentPadding = contentPadding, isPlus = isPlus, onAction = onAction, setShowPaywall = setShowPaywall, @@ -299,6 +319,7 @@ private fun SettingsScreen( isPlus = isPlus, serviceRunning = serviceRunning, settingsState = settingsState, + contentPadding = contentPadding, focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt index 377ac62..b070f26 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AlarmSettings.kt @@ -28,8 +28,10 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.clickable 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.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -43,6 +45,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.LargeFlexibleTopAppBar import androidx.compose.material3.ListItem +import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text @@ -57,6 +60,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -80,12 +84,14 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape @Composable fun AlarmSettings( settingsState: SettingsState, + contentPadding: PaddingValues, onAction: (SettingsAction) -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val context = LocalContext.current + val layoutDirection = LocalLayoutDirection.current var alarmName by remember { mutableStateOf("...") } @@ -148,32 +154,42 @@ fun AlarmSettings( ) } - Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { - LargeFlexibleTopAppBar( - title = { - Text(stringResource(R.string.alarm), fontFamily = robotoFlexTopBar) - }, - subtitle = { - Text(stringResource(R.string.settings)) - }, - navigationIcon = { - FilledTonalIconButton( - onClick = onBack, - shapes = IconButtonDefaults.shapes(), - colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor) - ) { - Icon( - painterResource(R.drawable.arrow_back), - null - ) - } - }, - colors = topBarColors, - scrollBehavior = scrollBehavior + Scaffold( + topBar = { + LargeFlexibleTopAppBar( + title = { + Text(stringResource(R.string.alarm), fontFamily = robotoFlexTopBar) + }, + subtitle = { + Text(stringResource(R.string.settings)) + }, + navigationIcon = { + FilledTonalIconButton( + onClick = onBack, + shapes = IconButtonDefaults.shapes(), + colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor) + ) { + Icon( + painterResource(R.drawable.arrow_back), + null + ) + } + }, + colors = topBarColors, + 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( verticalArrangement = Arrangement.spacedBy(2.dp), + contentPadding = insets, modifier = Modifier .background(topBarColors.containerColor) .fillMaxSize() @@ -248,6 +264,7 @@ fun AlarmSettingsPreview() { val settingsState = SettingsState() AlarmSettings( settingsState = settingsState, + contentPadding = PaddingValues(), onAction = {}, onBack = {} ) diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt index f61fd18..5da1ec9 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/AppearanceSettings.kt @@ -19,8 +19,10 @@ package org.nsh07.pomodoro.ui.settingsScreen.screens import androidx.compose.foundation.background 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.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -33,6 +35,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.LargeFlexibleTopAppBar import androidx.compose.material3.ListItem +import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text @@ -41,6 +44,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -64,6 +68,7 @@ import org.nsh07.pomodoro.utils.toColor @Composable fun AppearanceSettings( settingsState: SettingsState, + contentPadding: PaddingValues, isPlus: Boolean, onAction: (SettingsAction) -> Unit, setShowPaywall: (Boolean) -> Unit, @@ -71,33 +76,44 @@ fun AppearanceSettings( modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val layoutDirection = LocalLayoutDirection.current - Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { - LargeFlexibleTopAppBar( - title = { - Text(stringResource(R.string.appearance), fontFamily = robotoFlexTopBar) - }, - subtitle = { - Text(stringResource(R.string.settings)) - }, - navigationIcon = { - FilledTonalIconButton( - onClick = onBack, - shapes = IconButtonDefaults.shapes(), - colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor) - ) { - Icon( - painterResource(R.drawable.arrow_back), - null - ) - } - }, - colors = topBarColors, - scrollBehavior = scrollBehavior + Scaffold( + topBar = { + LargeFlexibleTopAppBar( + title = { + Text(stringResource(R.string.appearance), fontFamily = robotoFlexTopBar) + }, + subtitle = { + Text(stringResource(R.string.settings)) + }, + navigationIcon = { + FilledTonalIconButton( + onClick = onBack, + shapes = IconButtonDefaults.shapes(), + colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor) + ) { + Icon( + painterResource(R.drawable.arrow_back), + null + ) + } + }, + colors = topBarColors, + 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( verticalArrangement = Arrangement.spacedBy(2.dp), + contentPadding = insets, modifier = Modifier .background(topBarColors.containerColor) .fillMaxSize() @@ -182,6 +198,7 @@ fun AppearanceSettingsPreview() { TomatoTheme(dynamicColor = false) { AppearanceSettings( settingsState = settingsState, + contentPadding = PaddingValues(), isPlus = false, onAction = {}, setShowPaywall = {}, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt index d94b0f0..38a75a9 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/screens/TimerSettings.kt @@ -27,8 +27,11 @@ import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row 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.fillMaxWidth import androidx.compose.foundation.layout.height @@ -53,6 +56,7 @@ import androidx.compose.material3.ListItem import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.SliderState import androidx.compose.material3.Switch @@ -71,6 +75,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction @@ -98,6 +103,7 @@ fun TimerSettings( isPlus: Boolean, serviceRunning: Boolean, settingsState: SettingsState, + contentPadding: PaddingValues, focusTimeInputFieldState: TextFieldState, shortBreakTimeInputFieldState: TextFieldState, longBreakTimeInputFieldState: TextFieldState, @@ -109,6 +115,7 @@ fun TimerSettings( ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val context = LocalContext.current + val layoutDirection = LocalLayoutDirection.current val appName = stringResource(R.string.app_name) val notificationManagerService = remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } @@ -141,32 +148,42 @@ fun TimerSettings( ) ) - Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { - LargeFlexibleTopAppBar( - title = { - Text(stringResource(R.string.timer), fontFamily = robotoFlexTopBar) - }, - subtitle = { - Text(stringResource(R.string.settings)) - }, - navigationIcon = { - FilledTonalIconButton( - onClick = onBack, - shapes = IconButtonDefaults.shapes(), - colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor) - ) { - Icon( - painterResource(R.drawable.arrow_back), - null - ) - } - }, - colors = topBarColors, - scrollBehavior = scrollBehavior + Scaffold( + topBar = { + LargeFlexibleTopAppBar( + title = { + Text(stringResource(R.string.timer), fontFamily = robotoFlexTopBar) + }, + subtitle = { + Text(stringResource(R.string.settings)) + }, + navigationIcon = { + FilledTonalIconButton( + onClick = onBack, + shapes = IconButtonDefaults.shapes(), + colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor) + ) { + Icon( + painterResource(R.drawable.arrow_back), + null + ) + } + }, + colors = topBarColors, + 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( verticalArrangement = Arrangement.spacedBy(2.dp), + contentPadding = insets, modifier = Modifier .background(topBarColors.containerColor) .fillMaxSize() @@ -428,6 +445,7 @@ private fun TimerSettingsPreview() { isPlus = false, serviceRunning = true, settingsState = remember { SettingsState() }, + contentPadding = PaddingValues(), focusTimeInputFieldState = focusTimeInputFieldState, shortBreakTimeInputFieldState = shortBreakTimeInputFieldState, longBreakTimeInputFieldState = longBreakTimeInputFieldState, diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt index 42ad7e3..7a182bf 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/statsScreen/StatsScreen.kt @@ -27,6 +27,8 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer 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.height 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.motionScheme import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -61,6 +64,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalFontFamilyResolver +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -129,6 +133,7 @@ fun StatsScreen( modifier: Modifier = Modifier ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + val layoutDirection = LocalLayoutDirection.current val hoursFormat = stringResource(R.string.hours_format) val hoursMinutesFormat = stringResource(R.string.hours_and_minutes_format) @@ -160,43 +165,50 @@ fun StatsScreen( val axisTypeface = remember { resolver.resolve(googleFlex400).value as Typeface } val markerTypeface = remember { resolver.resolve(googleFlex600).value as Typeface } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection) - ) { - TopAppBar( - title = { - Text( - stringResource(R.string.stats), - style = LocalTextStyle.current.copy( - fontFamily = robotoFlexTopBar, - fontSize = 32.sp, - lineHeight = 32.sp - ), - modifier = Modifier - .padding(top = contentPadding.calculateTopPadding()) - .padding(vertical = 14.dp) - ) - }, - actions = if (BuildConfig.DEBUG) { - { - IconButton( - onClick = generateSampleData - ) { - Spacer(Modifier.size(24.dp)) + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + stringResource(R.string.stats), + style = LocalTextStyle.current.copy( + fontFamily = robotoFlexTopBar, + fontSize = 32.sp, + lineHeight = 32.sp + ), + modifier = Modifier + .padding(top = contentPadding.calculateTopPadding()) + .padding(vertical = 14.dp) + ) + }, + actions = if (BuildConfig.DEBUG) { + { + IconButton( + onClick = generateSampleData + ) { + Spacer(Modifier.size(24.dp)) + } } - } - } else { - {} - }, - subtitle = {}, - titleHorizontalAlignment = Alignment.CenterHorizontally, - scrollBehavior = scrollBehavior, - windowInsets = WindowInsets() + } else { + {} + }, + subtitle = {}, + titleHorizontalAlignment = Alignment.CenterHorizontally, + scrollBehavior = scrollBehavior, + 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( horizontalAlignment = Alignment.CenterHorizontally, + contentPadding = insets, verticalArrangement = Arrangement.spacedBy(16.dp) ) { item { Spacer(Modifier) } diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt index 662bf16..555ef59 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/timerScreen/TimerScreen.kt @@ -38,8 +38,11 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer 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.fillMaxWidth 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.width import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ButtonGroup import androidx.compose.material3.ButtonGroupDefaults 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.shapes import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text 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.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -107,12 +111,14 @@ import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState fun SharedTransitionScope.TimerScreen( timerState: TimerState, isPlus: Boolean, + contentPadding: PaddingValues, progress: () -> Float, onAction: (TimerAction) -> Unit, modifier: Modifier = Modifier ) { val motionScheme = motionScheme val haptic = LocalHapticFeedback.current + val layoutDirection = LocalLayoutDirection.current val color by animateColorAsState( if (timerState.timerMode == TimerMode.FOCUS) colorScheme.primary @@ -137,407 +143,424 @@ fun SharedTransitionScope.TimerScreen( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - Column(modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { - TopAppBar( - title = { - AnimatedContent( - if (!timerState.showBrandTitle) timerState.timerMode else TimerMode.BRAND, - transitionSpec = { - slideInVertically( - animationSpec = motionScheme.defaultSpatialSpec(), - initialOffsetY = { (-it * 1.25).toInt() } - ).togetherWith( - slideOutVertically( + Scaffold( + topBar = { + TopAppBar( + title = { + AnimatedContent( + if (!timerState.showBrandTitle) timerState.timerMode else TimerMode.BRAND, + transitionSpec = { + slideInVertically( animationSpec = motionScheme.defaultSpatialSpec(), - targetOffsetY = { (it * 1.25).toInt() } + initialOffsetY = { (-it * 1.25).toInt() } + ).togetherWith( + slideOutVertically( + animationSpec = motionScheme.defaultSpatialSpec(), + targetOffsetY = { (it * 1.25).toInt() } + ) ) - ) - }, - contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxWidth(.9f) - ) { - when (it) { - TimerMode.BRAND -> - Text( - if (!isPlus) stringResource(R.string.app_name) - else stringResource(R.string.app_name_plus), + }, + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxWidth(.9f) + ) { + when (it) { + TimerMode.BRAND -> + Text( + if (!isPlus) stringResource(R.string.app_name) + else stringResource(R.string.app_name_plus), + style = TextStyle( + fontFamily = robotoFlexTopBar, + fontSize = 32.sp, + lineHeight = 32.sp, + color = colorScheme.error + ), + textAlign = TextAlign.Center + ) + + TimerMode.FOCUS -> + Text( + stringResource(R.string.focus), + style = TextStyle( + fontFamily = robotoFlexTopBar, + fontSize = 32.sp, + lineHeight = 32.sp, + color = colorScheme.primary + ), + textAlign = TextAlign.Center + ) + + TimerMode.SHORT_BREAK -> Text( + stringResource(R.string.short_break), style = TextStyle( fontFamily = robotoFlexTopBar, fontSize = 32.sp, lineHeight = 32.sp, - color = colorScheme.error + color = colorScheme.tertiary ), textAlign = TextAlign.Center ) - TimerMode.FOCUS -> - Text( - stringResource(R.string.focus), + TimerMode.LONG_BREAK -> Text( + stringResource(R.string.long_break), style = TextStyle( fontFamily = robotoFlexTopBar, fontSize = 32.sp, lineHeight = 32.sp, - color = colorScheme.primary + color = colorScheme.tertiary ), textAlign = TextAlign.Center ) - - TimerMode.SHORT_BREAK -> Text( - stringResource(R.string.short_break), - style = TextStyle( - fontFamily = robotoFlexTopBar, - fontSize = 32.sp, - lineHeight = 32.sp, - color = colorScheme.tertiary - ), - textAlign = TextAlign.Center - ) - - TimerMode.LONG_BREAK -> Text( - stringResource(R.string.long_break), - style = TextStyle( - fontFamily = robotoFlexTopBar, - fontSize = 32.sp, - lineHeight = 32.sp, - color = colorScheme.tertiary - ), - textAlign = TextAlign.Center - ) + } } - } - }, - subtitle = {}, - titleHorizontalAlignment = CenterHorizontally, - scrollBehavior = scrollBehavior + }, + subtitle = {}, + titleHorizontalAlignment = CenterHorizontally, + 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) ) - - Column( + LazyColumn( verticalArrangement = Arrangement.Center, horizontalAlignment = CenterHorizontally, + contentPadding = insets, modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) ) { - Column(horizontalAlignment = CenterHorizontally) { - Box(contentAlignment = Alignment.Center) { - if (timerState.timerMode == TimerMode.FOCUS) { - CircularProgressIndicator( - progress = progress, - modifier = Modifier - .sharedBounds( - sharedContentState = this@TimerScreen.rememberSharedContentState( - "focus progress" - ), - animatedVisibilityScope = LocalNavAnimatedContentScope.current - ) - .widthIn(max = 350.dp) - .fillMaxWidth(0.9f) - .aspectRatio(1f), - color = color, - trackColor = colorContainer, - strokeWidth = 16.dp, - gapSize = 8.dp - ) - } else { - CircularWavyProgressIndicator( - progress = progress, - modifier = Modifier - .sharedBounds( - sharedContentState = this@TimerScreen.rememberSharedContentState( - "break progress" - ), - animatedVisibilityScope = LocalNavAnimatedContentScope.current - ) - .widthIn(max = 350.dp) - .fillMaxWidth(0.9f) - .aspectRatio(1f), - color = color, - trackColor = colorContainer, - stroke = Stroke( - width = with(LocalDensity.current) { - 16.dp.toPx() - }, - cap = StrokeCap.Round, - ), - trackStroke = Stroke( - width = with(LocalDensity.current) { - 16.dp.toPx() - }, - cap = StrokeCap.Round, - ), - wavelength = 60.dp, - gapSize = 8.dp - ) - } - var expanded by remember { mutableStateOf(timerState.showBrandTitle) } - Column( - horizontalAlignment = CenterHorizontally, - modifier = Modifier - .clip(shapes.largeIncreased) - .clickable(onClick = { expanded = !expanded }) - ) { - LaunchedEffect(timerState.showBrandTitle) { - expanded = timerState.showBrandTitle - } - Text( - text = timerState.timeStr, - style = TextStyle( - fontFamily = googleFlex600, - fontSize = 72.sp, - letterSpacing = (-2.6).sp, - fontFeatureSettings = "tnum" - ), - textAlign = TextAlign.Center, - maxLines = 1, - modifier = Modifier.sharedBounds( - sharedContentState = this@TimerScreen.rememberSharedContentState("clock"), - animatedVisibilityScope = LocalNavAnimatedContentScope.current - ) - ) - AnimatedVisibility( - expanded, - enter = fadeIn(motionScheme.defaultEffectsSpec()) + - expandVertically(motionScheme.defaultSpatialSpec()), - exit = fadeOut(motionScheme.defaultEffectsSpec()) + - shrinkVertically(motionScheme.defaultSpatialSpec()) - ) { - Text( - stringResource( - R.string.timer_session_count, - timerState.currentFocusCount, - timerState.totalFocusCount - ), - fontFamily = googleFlex600, - style = typography.titleLarge, - color = colorScheme.outline - ) - } - } - } - val interactionSources = remember { List(3) { MutableInteractionSource() } } - ButtonGroup( - overflowIndicator = { state -> - ButtonGroupDefaults.OverflowIndicator( - state, - colors = IconButtonDefaults.filledTonalIconButtonColors(), - modifier = Modifier.size(64.dp, 96.dp) - ) - }, - modifier = Modifier.padding(16.dp) - ) { - customItem( - { - FilledIconToggleButton( - onCheckedChange = { checked -> - onAction(TimerAction.ToggleTimer) - - if (checked) haptic.performHapticFeedback(HapticFeedbackType.ToggleOn) - else haptic.performHapticFeedback(HapticFeedbackType.ToggleOff) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && checked) { - permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) - } - }, - checked = timerState.timerRunning, - colors = IconButtonDefaults.filledIconToggleButtonColors( - checkedContainerColor = color, - checkedContentColor = onColor - ), - shapes = IconButtonDefaults.toggleableShapes(), - interactionSource = interactionSources[0], + item { + Column(horizontalAlignment = CenterHorizontally) { + Box(contentAlignment = Alignment.Center) { + if (timerState.timerMode == TimerMode.FOCUS) { + CircularProgressIndicator( + progress = progress, modifier = Modifier - .size(width = 128.dp, height = 96.dp) - .animateWidth(interactionSources[0]) - ) { - if (timerState.timerRunning) { - Icon( - painterResource(R.drawable.pause_large), - contentDescription = stringResource(R.string.pause), - modifier = Modifier.size(32.dp) + .sharedBounds( + sharedContentState = this@TimerScreen.rememberSharedContentState( + "focus progress" + ), + animatedVisibilityScope = LocalNavAnimatedContentScope.current ) - } else { - Icon( - painterResource(R.drawable.play_large), - contentDescription = stringResource(R.string.play), - modifier = Modifier.size(32.dp) + .widthIn(max = 350.dp) + .fillMaxWidth(0.9f) + .aspectRatio(1f), + color = color, + trackColor = colorContainer, + strokeWidth = 16.dp, + gapSize = 8.dp + ) + } else { + CircularWavyProgressIndicator( + progress = progress, + modifier = Modifier + .sharedBounds( + sharedContentState = this@TimerScreen.rememberSharedContentState( + "break progress" + ), + animatedVisibilityScope = LocalNavAnimatedContentScope.current ) - } + .widthIn(max = 350.dp) + .fillMaxWidth(0.9f) + .aspectRatio(1f), + color = color, + trackColor = colorContainer, + stroke = Stroke( + width = with(LocalDensity.current) { + 16.dp.toPx() + }, + cap = StrokeCap.Round, + ), + trackStroke = Stroke( + width = with(LocalDensity.current) { + 16.dp.toPx() + }, + cap = StrokeCap.Round, + ), + wavelength = 60.dp, + gapSize = 8.dp + ) + } + var expanded by remember { mutableStateOf(timerState.showBrandTitle) } + Column( + horizontalAlignment = CenterHorizontally, + modifier = Modifier + .clip(shapes.largeIncreased) + .clickable(onClick = { expanded = !expanded }) + ) { + LaunchedEffect(timerState.showBrandTitle) { + expanded = timerState.showBrandTitle } + Text( + text = timerState.timeStr, + style = TextStyle( + fontFamily = googleFlex600, + fontSize = 72.sp, + letterSpacing = (-2.6).sp, + fontFeatureSettings = "tnum" + ), + textAlign = TextAlign.Center, + maxLines = 1, + modifier = Modifier.sharedBounds( + sharedContentState = this@TimerScreen.rememberSharedContentState( + "clock" + ), + animatedVisibilityScope = LocalNavAnimatedContentScope.current + ) + ) + AnimatedVisibility( + expanded, + enter = fadeIn(motionScheme.defaultEffectsSpec()) + + expandVertically(motionScheme.defaultSpatialSpec()), + exit = fadeOut(motionScheme.defaultEffectsSpec()) + + shrinkVertically(motionScheme.defaultSpatialSpec()) + ) { + Text( + stringResource( + R.string.timer_session_count, + timerState.currentFocusCount, + timerState.totalFocusCount + ), + fontFamily = googleFlex600, + style = typography.titleLarge, + color = colorScheme.outline + ) + } + } + } + val interactionSources = remember { List(3) { MutableInteractionSource() } } + ButtonGroup( + overflowIndicator = { state -> + ButtonGroupDefaults.OverflowIndicator( + state, + colors = IconButtonDefaults.filledTonalIconButtonColors(), + modifier = Modifier.size(64.dp, 96.dp) + ) }, - { state -> - DropdownMenuItem( - leadingIcon = { + modifier = Modifier.padding(16.dp) + ) { + customItem( + { + FilledIconToggleButton( + onCheckedChange = { checked -> + onAction(TimerAction.ToggleTimer) + + if (checked) haptic.performHapticFeedback(HapticFeedbackType.ToggleOn) + else haptic.performHapticFeedback(HapticFeedbackType.ToggleOff) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && checked) { + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + }, + checked = timerState.timerRunning, + colors = IconButtonDefaults.filledIconToggleButtonColors( + checkedContainerColor = color, + checkedContentColor = onColor + ), + shapes = IconButtonDefaults.toggleableShapes(), + interactionSource = interactionSources[0], + modifier = Modifier + .size(width = 128.dp, height = 96.dp) + .animateWidth(interactionSources[0]) + ) { if (timerState.timerRunning) { Icon( - painterResource(R.drawable.pause), - contentDescription = stringResource(R.string.pause) + painterResource(R.drawable.pause_large), + contentDescription = stringResource(R.string.pause), + modifier = Modifier.size(32.dp) ) } else { Icon( - painterResource(R.drawable.play), - contentDescription = stringResource(R.string.play) + painterResource(R.drawable.play_large), + contentDescription = stringResource(R.string.play), + modifier = Modifier.size(32.dp) ) } - }, - text = { - Text( - if (timerState.timerRunning) stringResource(R.string.pause) else stringResource( - R.string.play + } + }, + { state -> + DropdownMenuItem( + leadingIcon = { + if (timerState.timerRunning) { + Icon( + painterResource(R.drawable.pause), + contentDescription = stringResource(R.string.pause) + ) + } else { + Icon( + painterResource(R.drawable.play), + contentDescription = stringResource(R.string.play) + ) + } + }, + text = { + Text( + if (timerState.timerRunning) stringResource(R.string.pause) else stringResource( + R.string.play + ) ) - ) - }, - onClick = { - onAction(TimerAction.ToggleTimer) - state.dismiss() - } - ) - } - ) - - customItem( - { - FilledTonalIconButton( - onClick = { - onAction(TimerAction.ResetTimer) - haptic.performHapticFeedback(HapticFeedbackType.VirtualKey) - }, - colors = IconButtonDefaults.filledTonalIconButtonColors( - containerColor = colorContainer - ), - shapes = IconButtonDefaults.shapes(), - interactionSource = interactionSources[1], - modifier = Modifier - .size(96.dp) - .animateWidth(interactionSources[1]) - ) { - Icon( - painterResource(R.drawable.restart_large), - contentDescription = stringResource(R.string.restart), - modifier = Modifier.size(32.dp) + }, + onClick = { + onAction(TimerAction.ToggleTimer) + state.dismiss() + } ) } - }, - { state -> - DropdownMenuItem( - leadingIcon = { - Icon( - painterResource(R.drawable.restart), - stringResource(R.string.restart) - ) - }, - text = { Text(stringResource(R.string.restart)) }, - onClick = { - onAction(TimerAction.ResetTimer) - state.dismiss() - } - ) - } - ) + ) - customItem( - { - FilledTonalIconButton( - onClick = { - onAction(TimerAction.SkipTimer(fromButton = true)) - haptic.performHapticFeedback(HapticFeedbackType.VirtualKey) - }, - colors = IconButtonDefaults.filledTonalIconButtonColors( - containerColor = colorContainer - ), - shapes = IconButtonDefaults.shapes(), - interactionSource = interactionSources[2], - modifier = Modifier - .size(64.dp, 96.dp) - .animateWidth(interactionSources[2]) - ) { - Icon( - painterResource(R.drawable.skip_next_large), - contentDescription = stringResource(R.string.skip_to_next), - modifier = Modifier.size(32.dp) + customItem( + { + FilledTonalIconButton( + onClick = { + onAction(TimerAction.ResetTimer) + haptic.performHapticFeedback(HapticFeedbackType.VirtualKey) + }, + colors = IconButtonDefaults.filledTonalIconButtonColors( + containerColor = colorContainer + ), + shapes = IconButtonDefaults.shapes(), + interactionSource = interactionSources[1], + modifier = Modifier + .size(96.dp) + .animateWidth(interactionSources[1]) + ) { + Icon( + painterResource(R.drawable.restart_large), + contentDescription = stringResource(R.string.restart), + modifier = Modifier.size(32.dp) + ) + } + }, + { state -> + DropdownMenuItem( + leadingIcon = { + Icon( + painterResource(R.drawable.restart), + stringResource(R.string.restart) + ) + }, + text = { Text(stringResource(R.string.restart)) }, + onClick = { + onAction(TimerAction.ResetTimer) + state.dismiss() + } ) } - }, - { state -> - DropdownMenuItem( - leadingIcon = { + ) + + customItem( + { + FilledTonalIconButton( + onClick = { + onAction(TimerAction.SkipTimer(fromButton = true)) + haptic.performHapticFeedback(HapticFeedbackType.VirtualKey) + }, + colors = IconButtonDefaults.filledTonalIconButtonColors( + containerColor = colorContainer + ), + shapes = IconButtonDefaults.shapes(), + interactionSource = interactionSources[2], + modifier = Modifier + .size(64.dp, 96.dp) + .animateWidth(interactionSources[2]) + ) { Icon( - painterResource(R.drawable.skip_next), - stringResource(R.string.skip_to_next) + painterResource(R.drawable.skip_next_large), + contentDescription = stringResource(R.string.skip_to_next), + modifier = Modifier.size(32.dp) ) - }, - text = { Text(stringResource(R.string.skip_to_next)) }, - onClick = { - onAction(TimerAction.SkipTimer(fromButton = true)) - state.dismiss() } - ) - } - ) + }, + { state -> + DropdownMenuItem( + leadingIcon = { + Icon( + painterResource(R.drawable.skip_next), + stringResource(R.string.skip_to_next) + ) + }, + text = { Text(stringResource(R.string.skip_to_next)) }, + onClick = { + onAction(TimerAction.SkipTimer(fromButton = true)) + state.dismiss() + } + ) + } + ) + } } } - Spacer(Modifier.height(32.dp)) + item { Spacer(Modifier.height(32.dp)) } - Column(horizontalAlignment = CenterHorizontally) { - Text(stringResource(R.string.up_next), style = typography.titleSmall) - AnimatedContent( - timerState.nextTimeStr, - transitionSpec = { - slideInVertically( - animationSpec = motionScheme.defaultSpatialSpec(), - initialOffsetY = { (-it * 1.25).toInt() } - ).togetherWith( - slideOutVertically( + item { + Column(horizontalAlignment = CenterHorizontally) { + Text(stringResource(R.string.up_next), style = typography.titleSmall) + AnimatedContent( + timerState.nextTimeStr, + transitionSpec = { + slideInVertically( animationSpec = motionScheme.defaultSpatialSpec(), - targetOffsetY = { (it * 1.25).toInt() } + initialOffsetY = { (-it * 1.25).toInt() } + ).togetherWith( + slideOutVertically( + animationSpec = motionScheme.defaultSpatialSpec(), + targetOffsetY = { (it * 1.25).toInt() } + ) ) + } + ) { + Text( + it, + style = TextStyle( + fontFamily = googleFlex600, + fontSize = 22.sp, + lineHeight = 28.sp, + color = if (timerState.nextTimerMode == TimerMode.FOCUS) colorScheme.primary else colorScheme.tertiary, + textAlign = TextAlign.Center + ), + modifier = Modifier.width(200.dp) ) } - ) { - Text( - it, - style = TextStyle( - fontFamily = googleFlex600, - fontSize = 22.sp, - lineHeight = 28.sp, - color = if (timerState.nextTimerMode == TimerMode.FOCUS) colorScheme.primary else colorScheme.tertiary, - textAlign = TextAlign.Center - ), - modifier = Modifier.width(200.dp) - ) - } - AnimatedContent( - timerState.nextTimerMode, - transitionSpec = { - slideInVertically( - animationSpec = motionScheme.defaultSpatialSpec(), - initialOffsetY = { (-it * 1.25).toInt() } - ).togetherWith( - slideOutVertically( + AnimatedContent( + timerState.nextTimerMode, + transitionSpec = { + slideInVertically( animationSpec = motionScheme.defaultSpatialSpec(), - targetOffsetY = { (it * 1.25).toInt() } + initialOffsetY = { (-it * 1.25).toInt() } + ).togetherWith( + slideOutVertically( + animationSpec = motionScheme.defaultSpatialSpec(), + targetOffsetY = { (it * 1.25).toInt() } + ) ) + } + ) { + Text( + when (it) { + TimerMode.FOCUS -> stringResource(R.string.focus) + TimerMode.SHORT_BREAK -> stringResource(R.string.short_break) + else -> stringResource(R.string.long_break) + }, + style = typography.titleMediumEmphasized, + textAlign = TextAlign.Center, + modifier = Modifier.width(200.dp) ) } - ) { - Text( - when (it) { - TimerMode.FOCUS -> stringResource(R.string.focus) - TimerMode.SHORT_BREAK -> stringResource(R.string.short_break) - else -> stringResource(R.string.long_break) - }, - style = typography.titleMediumEmphasized, - textAlign = TextAlign.Center, - modifier = Modifier.width(200.dp) - ) } } - Spacer(Modifier.height(16.dp)) + item { Spacer(Modifier.height(16.dp)) } } } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Preview( showSystemUi = true, device = Devices.PIXEL_9_PRO @@ -553,6 +576,7 @@ fun TimerScreenPreview() { TimerScreen( timerState, isPlus = true, + contentPadding = PaddingValues(), { 0.3f }, {} )