feat(ui): use toolbar instead of navigation bar for navigation
Closes: #111
This commit is contained in:
@@ -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
|
||||||
if (backStack.size < 2) backStack.add(it.route)
|
),
|
||||||
else backStack[1] = it.route
|
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 ->
|
) { 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(
|
if (backStack.size < 2) backStack.add(Screen.AOD)
|
||||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
} else Modifier
|
||||||
end = contentPadding.calculateEndPadding(layoutDirection),
|
|
||||||
bottom = contentPadding.calculateBottomPadding()
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
if (isAODEnabled) Modifier.clickable {
|
|
||||||
if (backStack.size < 2) backStack.add(Screen.AOD)
|
|
||||||
}
|
|
||||||
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 (backStack.size > 1) backStack.removeLastOrNull()
|
||||||
if (isAODEnabled) Modifier.clickable {
|
} else Modifier
|
||||||
if (backStack.size > 1) backStack.removeLastOrNull()
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,26 +189,36 @@ private fun SettingsScreen(
|
|||||||
},
|
},
|
||||||
entryProvider = entryProvider {
|
entryProvider = entryProvider {
|
||||||
entry<Screen.Settings.Main> {
|
entry<Screen.Settings.Main> {
|
||||||
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
|
Scaffold(
|
||||||
TopAppBar(
|
topBar = {
|
||||||
title = {
|
TopAppBar(
|
||||||
Text(
|
title = {
|
||||||
stringResource(R.string.settings),
|
Text(
|
||||||
style = LocalTextStyle.current.copy(
|
stringResource(R.string.settings),
|
||||||
fontFamily = robotoFlexTopBar,
|
style = LocalTextStyle.current.copy(
|
||||||
fontSize = 32.sp,
|
fontFamily = robotoFlexTopBar,
|
||||||
lineHeight = 32.sp
|
fontSize = 32.sp,
|
||||||
|
lineHeight = 32.sp
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
},
|
||||||
},
|
subtitle = {},
|
||||||
subtitle = {},
|
colors = topBarColors,
|
||||||
colors = topBarColors,
|
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,
|
||||||
|
|||||||
@@ -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,32 +154,42 @@ fun AlarmSettings(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
|
Scaffold(
|
||||||
LargeFlexibleTopAppBar(
|
topBar = {
|
||||||
title = {
|
LargeFlexibleTopAppBar(
|
||||||
Text(stringResource(R.string.alarm), fontFamily = robotoFlexTopBar)
|
title = {
|
||||||
},
|
Text(stringResource(R.string.alarm), fontFamily = robotoFlexTopBar)
|
||||||
subtitle = {
|
},
|
||||||
Text(stringResource(R.string.settings))
|
subtitle = {
|
||||||
},
|
Text(stringResource(R.string.settings))
|
||||||
navigationIcon = {
|
},
|
||||||
FilledTonalIconButton(
|
navigationIcon = {
|
||||||
onClick = onBack,
|
FilledTonalIconButton(
|
||||||
shapes = IconButtonDefaults.shapes(),
|
onClick = onBack,
|
||||||
colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor)
|
shapes = IconButtonDefaults.shapes(),
|
||||||
) {
|
colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor)
|
||||||
Icon(
|
) {
|
||||||
painterResource(R.drawable.arrow_back),
|
Icon(
|
||||||
null
|
painterResource(R.drawable.arrow_back),
|
||||||
)
|
null
|
||||||
}
|
)
|
||||||
},
|
}
|
||||||
colors = topBarColors,
|
},
|
||||||
scrollBehavior = scrollBehavior
|
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(
|
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 = {}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,33 +76,44 @@ 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(
|
||||||
LargeFlexibleTopAppBar(
|
topBar = {
|
||||||
title = {
|
LargeFlexibleTopAppBar(
|
||||||
Text(stringResource(R.string.appearance), fontFamily = robotoFlexTopBar)
|
title = {
|
||||||
},
|
Text(stringResource(R.string.appearance), fontFamily = robotoFlexTopBar)
|
||||||
subtitle = {
|
},
|
||||||
Text(stringResource(R.string.settings))
|
subtitle = {
|
||||||
},
|
Text(stringResource(R.string.settings))
|
||||||
navigationIcon = {
|
},
|
||||||
FilledTonalIconButton(
|
navigationIcon = {
|
||||||
onClick = onBack,
|
FilledTonalIconButton(
|
||||||
shapes = IconButtonDefaults.shapes(),
|
onClick = onBack,
|
||||||
colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor)
|
shapes = IconButtonDefaults.shapes(),
|
||||||
) {
|
colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor)
|
||||||
Icon(
|
) {
|
||||||
painterResource(R.drawable.arrow_back),
|
Icon(
|
||||||
null
|
painterResource(R.drawable.arrow_back),
|
||||||
)
|
null
|
||||||
}
|
)
|
||||||
},
|
}
|
||||||
colors = topBarColors,
|
},
|
||||||
scrollBehavior = scrollBehavior
|
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(
|
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 = {},
|
||||||
|
|||||||
@@ -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,32 +148,42 @@ fun TimerSettings(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
|
Scaffold(
|
||||||
LargeFlexibleTopAppBar(
|
topBar = {
|
||||||
title = {
|
LargeFlexibleTopAppBar(
|
||||||
Text(stringResource(R.string.timer), fontFamily = robotoFlexTopBar)
|
title = {
|
||||||
},
|
Text(stringResource(R.string.timer), fontFamily = robotoFlexTopBar)
|
||||||
subtitle = {
|
},
|
||||||
Text(stringResource(R.string.settings))
|
subtitle = {
|
||||||
},
|
Text(stringResource(R.string.settings))
|
||||||
navigationIcon = {
|
},
|
||||||
FilledTonalIconButton(
|
navigationIcon = {
|
||||||
onClick = onBack,
|
FilledTonalIconButton(
|
||||||
shapes = IconButtonDefaults.shapes(),
|
onClick = onBack,
|
||||||
colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor)
|
shapes = IconButtonDefaults.shapes(),
|
||||||
) {
|
colors = IconButtonDefaults.filledTonalIconButtonColors(containerColor = listItemColors.containerColor)
|
||||||
Icon(
|
) {
|
||||||
painterResource(R.drawable.arrow_back),
|
Icon(
|
||||||
null
|
painterResource(R.drawable.arrow_back),
|
||||||
)
|
null
|
||||||
}
|
)
|
||||||
},
|
}
|
||||||
colors = topBarColors,
|
},
|
||||||
scrollBehavior = scrollBehavior
|
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(
|
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,
|
||||||
|
|||||||
@@ -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,43 +165,50 @@ 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(
|
||||||
) {
|
title = {
|
||||||
TopAppBar(
|
Text(
|
||||||
title = {
|
stringResource(R.string.stats),
|
||||||
Text(
|
style = LocalTextStyle.current.copy(
|
||||||
stringResource(R.string.stats),
|
fontFamily = robotoFlexTopBar,
|
||||||
style = LocalTextStyle.current.copy(
|
fontSize = 32.sp,
|
||||||
fontFamily = robotoFlexTopBar,
|
lineHeight = 32.sp
|
||||||
fontSize = 32.sp,
|
),
|
||||||
lineHeight = 32.sp
|
modifier = Modifier
|
||||||
),
|
.padding(top = contentPadding.calculateTopPadding())
|
||||||
modifier = Modifier
|
.padding(vertical = 14.dp)
|
||||||
.padding(top = contentPadding.calculateTopPadding())
|
)
|
||||||
.padding(vertical = 14.dp)
|
},
|
||||||
)
|
actions = if (BuildConfig.DEBUG) {
|
||||||
},
|
{
|
||||||
actions = if (BuildConfig.DEBUG) {
|
IconButton(
|
||||||
{
|
onClick = generateSampleData
|
||||||
IconButton(
|
) {
|
||||||
onClick = generateSampleData
|
Spacer(Modifier.size(24.dp))
|
||||||
) {
|
}
|
||||||
Spacer(Modifier.size(24.dp))
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
{}
|
||||||
{}
|
},
|
||||||
},
|
subtitle = {},
|
||||||
subtitle = {},
|
titleHorizontalAlignment = Alignment.CenterHorizontally,
|
||||||
titleHorizontalAlignment = Alignment.CenterHorizontally,
|
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) }
|
||||||
|
|||||||
@@ -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,407 +143,424 @@ fun SharedTransitionScope.TimerScreen(
|
|||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
|
|
||||||
Column(modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
|
Scaffold(
|
||||||
TopAppBar(
|
topBar = {
|
||||||
title = {
|
TopAppBar(
|
||||||
AnimatedContent(
|
title = {
|
||||||
if (!timerState.showBrandTitle) timerState.timerMode else TimerMode.BRAND,
|
AnimatedContent(
|
||||||
transitionSpec = {
|
if (!timerState.showBrandTitle) timerState.timerMode else TimerMode.BRAND,
|
||||||
slideInVertically(
|
transitionSpec = {
|
||||||
animationSpec = motionScheme.defaultSpatialSpec(),
|
slideInVertically(
|
||||||
initialOffsetY = { (-it * 1.25).toInt() }
|
|
||||||
).togetherWith(
|
|
||||||
slideOutVertically(
|
|
||||||
animationSpec = motionScheme.defaultSpatialSpec(),
|
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,
|
||||||
contentAlignment = Alignment.Center,
|
modifier = Modifier.fillMaxWidth(.9f)
|
||||||
modifier = Modifier.fillMaxWidth(.9f)
|
) {
|
||||||
) {
|
when (it) {
|
||||||
when (it) {
|
TimerMode.BRAND ->
|
||||||
TimerMode.BRAND ->
|
Text(
|
||||||
Text(
|
if (!isPlus) stringResource(R.string.app_name)
|
||||||
if (!isPlus) stringResource(R.string.app_name)
|
else stringResource(R.string.app_name_plus),
|
||||||
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(
|
style = TextStyle(
|
||||||
fontFamily = robotoFlexTopBar,
|
fontFamily = robotoFlexTopBar,
|
||||||
fontSize = 32.sp,
|
fontSize = 32.sp,
|
||||||
lineHeight = 32.sp,
|
lineHeight = 32.sp,
|
||||||
color = colorScheme.error
|
color = colorScheme.tertiary
|
||||||
),
|
),
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
TimerMode.FOCUS ->
|
TimerMode.LONG_BREAK -> Text(
|
||||||
Text(
|
stringResource(R.string.long_break),
|
||||||
stringResource(R.string.focus),
|
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontFamily = robotoFlexTopBar,
|
fontFamily = robotoFlexTopBar,
|
||||||
fontSize = 32.sp,
|
fontSize = 32.sp,
|
||||||
lineHeight = 32.sp,
|
lineHeight = 32.sp,
|
||||||
color = colorScheme.primary
|
color = colorScheme.tertiary
|
||||||
),
|
),
|
||||||
textAlign = TextAlign.Center
|
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 = {},
|
||||||
subtitle = {},
|
titleHorizontalAlignment = CenterHorizontally,
|
||||||
titleHorizontalAlignment = 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(
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = CenterHorizontally,
|
horizontalAlignment = CenterHorizontally,
|
||||||
|
contentPadding = insets,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = CenterHorizontally) {
|
item {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Column(horizontalAlignment = CenterHorizontally) {
|
||||||
if (timerState.timerMode == TimerMode.FOCUS) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
CircularProgressIndicator(
|
if (timerState.timerMode == TimerMode.FOCUS) {
|
||||||
progress = progress,
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
progress = progress,
|
||||||
.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],
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 128.dp, height = 96.dp)
|
.sharedBounds(
|
||||||
.animateWidth(interactionSources[0])
|
sharedContentState = this@TimerScreen.rememberSharedContentState(
|
||||||
) {
|
"focus progress"
|
||||||
if (timerState.timerRunning) {
|
),
|
||||||
Icon(
|
animatedVisibilityScope = LocalNavAnimatedContentScope.current
|
||||||
painterResource(R.drawable.pause_large),
|
|
||||||
contentDescription = stringResource(R.string.pause),
|
|
||||||
modifier = Modifier.size(32.dp)
|
|
||||||
)
|
)
|
||||||
} else {
|
.widthIn(max = 350.dp)
|
||||||
Icon(
|
.fillMaxWidth(0.9f)
|
||||||
painterResource(R.drawable.play_large),
|
.aspectRatio(1f),
|
||||||
contentDescription = stringResource(R.string.play),
|
color = color,
|
||||||
modifier = Modifier.size(32.dp)
|
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 ->
|
modifier = Modifier.padding(16.dp)
|
||||||
DropdownMenuItem(
|
) {
|
||||||
leadingIcon = {
|
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) {
|
if (timerState.timerRunning) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.pause),
|
painterResource(R.drawable.pause_large),
|
||||||
contentDescription = stringResource(R.string.pause)
|
contentDescription = stringResource(R.string.pause),
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.play),
|
painterResource(R.drawable.play_large),
|
||||||
contentDescription = stringResource(R.string.play)
|
contentDescription = stringResource(R.string.play),
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
text = {
|
},
|
||||||
Text(
|
{ state ->
|
||||||
if (timerState.timerRunning) stringResource(R.string.pause) else stringResource(
|
DropdownMenuItem(
|
||||||
R.string.play
|
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 = {
|
||||||
onClick = {
|
onAction(TimerAction.ToggleTimer)
|
||||||
onAction(TimerAction.ToggleTimer)
|
state.dismiss()
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
{ 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(
|
customItem(
|
||||||
{
|
{
|
||||||
FilledTonalIconButton(
|
FilledTonalIconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onAction(TimerAction.SkipTimer(fromButton = true))
|
onAction(TimerAction.ResetTimer)
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.VirtualKey)
|
haptic.performHapticFeedback(HapticFeedbackType.VirtualKey)
|
||||||
},
|
},
|
||||||
colors = IconButtonDefaults.filledTonalIconButtonColors(
|
colors = IconButtonDefaults.filledTonalIconButtonColors(
|
||||||
containerColor = colorContainer
|
containerColor = colorContainer
|
||||||
),
|
),
|
||||||
shapes = IconButtonDefaults.shapes(),
|
shapes = IconButtonDefaults.shapes(),
|
||||||
interactionSource = interactionSources[2],
|
interactionSource = interactionSources[1],
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(64.dp, 96.dp)
|
.size(96.dp)
|
||||||
.animateWidth(interactionSources[2])
|
.animateWidth(interactionSources[1])
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.skip_next_large),
|
painterResource(R.drawable.restart_large),
|
||||||
contentDescription = stringResource(R.string.skip_to_next),
|
contentDescription = stringResource(R.string.restart),
|
||||||
modifier = Modifier.size(32.dp)
|
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(
|
customItem(
|
||||||
leadingIcon = {
|
{
|
||||||
|
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(
|
Icon(
|
||||||
painterResource(R.drawable.skip_next),
|
painterResource(R.drawable.skip_next_large),
|
||||||
stringResource(R.string.skip_to_next)
|
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) {
|
item {
|
||||||
Text(stringResource(R.string.up_next), style = typography.titleSmall)
|
Column(horizontalAlignment = CenterHorizontally) {
|
||||||
AnimatedContent(
|
Text(stringResource(R.string.up_next), style = typography.titleSmall)
|
||||||
timerState.nextTimeStr,
|
AnimatedContent(
|
||||||
transitionSpec = {
|
timerState.nextTimeStr,
|
||||||
slideInVertically(
|
transitionSpec = {
|
||||||
animationSpec = motionScheme.defaultSpatialSpec(),
|
slideInVertically(
|
||||||
initialOffsetY = { (-it * 1.25).toInt() }
|
|
||||||
).togetherWith(
|
|
||||||
slideOutVertically(
|
|
||||||
animationSpec = motionScheme.defaultSpatialSpec(),
|
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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
AnimatedContent(
|
||||||
Text(
|
timerState.nextTimerMode,
|
||||||
it,
|
transitionSpec = {
|
||||||
style = TextStyle(
|
slideInVertically(
|
||||||
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(
|
|
||||||
animationSpec = motionScheme.defaultSpatialSpec(),
|
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(
|
@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 },
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user