feat: multiple checkpoints

This commit is contained in:
Acclorite 2025-07-05 16:34:00 +03:00
parent 57afade51a
commit 754a0d7cf3
No known key found for this signature in database
GPG key ID: 6E54C611F6EE8593
9 changed files with 273 additions and 191 deletions

View file

@ -9,6 +9,7 @@ package ua.acclorite.book_story.presentation.reader
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter
import ua.acclorite.book_story.presentation.reader.model.Checkpoint
@Immutable @Immutable
sealed class ReaderEvent { sealed class ReaderEvent {
@ -39,7 +40,9 @@ sealed class ReaderEvent {
val progress: Float val progress: Float
) : ReaderEvent() ) : ReaderEvent()
data object OnRestoreCheckpoint : ReaderEvent() data class OnRestoreCheckpoint(
val checkpoint: Checkpoint
) : ReaderEvent()
data class OnLeave( data class OnLeave(
val activity: ComponentActivity, val activity: ComponentActivity,

View file

@ -143,7 +143,7 @@ class ReaderModel @Inject constructor(
} }
is ReaderEvent.OnMenuVisibility -> { is ReaderEvent.OnMenuVisibility -> {
launch { launch(Dispatchers.Default) {
if (_state.value.lockMenu) return@launch if (_state.value.lockMenu) return@launch
yield() yield()
@ -152,21 +152,31 @@ class ReaderModel @Inject constructor(
show = event.show || !event.fullscreenMode, show = event.show || !event.fullscreenMode,
activity = event.activity activity = event.activity
) )
val checkpoints = _state.value.checkpoints.toMutableList()
if (event.saveCheckpoint && event.show) {
checkpoints.removeIf {
it.index == _state.value.listState.firstVisibleItemIndex
}
checkpoints.add(
Checkpoint(
_state.value.listState.firstVisibleItemIndex,
_state.value.listState.firstVisibleItemScrollOffset
)
)
}
_state.update { _state.update {
it.copy( it.copy(
showMenu = event.show, showMenu = event.show,
checkpoint = _state.value.listState.run { checkpoints = checkpoints
if (!event.show || !event.saveCheckpoint) return@run it.checkpoint
Checkpoint(firstVisibleItemIndex, firstVisibleItemScrollOffset)
}
) )
} }
} }
} }
is ReaderEvent.OnChangeProgress -> { is ReaderEvent.OnChangeProgress -> {
launch(Dispatchers.IO) { launch(Dispatchers.Default) {
_state.update { _state.update {
it.copy( it.copy(
book = it.book.copy( book = it.book.copy(
@ -185,7 +195,7 @@ class ReaderModel @Inject constructor(
} }
is ReaderEvent.OnScrollToChapter -> { is ReaderEvent.OnScrollToChapter -> {
launch { launch(Dispatchers.Default) {
_state.value.apply { _state.value.apply {
val chapterIndex = text.indexOf(event.chapter).takeIf { it != -1 } val chapterIndex = text.indexOf(event.chapter).takeIf { it != -1 }
if (chapterIndex == null) { if (chapterIndex == null) {
@ -207,40 +217,52 @@ class ReaderModel @Inject constructor(
is ReaderEvent.OnScroll -> { is ReaderEvent.OnScroll -> {
scrollJob?.cancel() scrollJob?.cancel()
scrollJob = launch { scrollJob = launch(Dispatchers.Main) {
delay(300) delay(300)
yield()
val scrollTo = (_state.value.text.lastIndex * event.progress).roundToInt() val scrollTo = (_state.value.text.lastIndex * event.progress).roundToInt()
_state.value.listState.requestScrollToItem(scrollTo)
updateChapter(scrollTo) updateChapter(scrollTo)
} try {
} _state.value.listState.requestScrollToItem(scrollTo)
} catch (_: Exception) {
is ReaderEvent.OnRestoreCheckpoint -> {
launch {
_state.value.apply {
listState.requestScrollToItem(
checkpoint.index,
checkpoint.offset
)
updateChapter(checkpoint.index)
onEvent(
ReaderEvent.OnChangeProgress(
progress = calculateProgress(checkpoint.index),
firstVisibleItemIndex = checkpoint.index,
firstVisibleItemOffset = checkpoint.offset,
)
)
} }
} }
} }
is ReaderEvent.OnLeave -> { is ReaderEvent.OnRestoreCheckpoint -> {
launch { launch(Dispatchers.Default) {
yield() _state.update {
val checkpoints = it.checkpoints.toMutableList()
if (checkpoints.size > 1) checkpoints.remove(event.checkpoint)
it.copy(
checkpoints = checkpoints
)
}
try {
_state.value.listState.requestScrollToItem(
event.checkpoint.index,
event.checkpoint.offset
)
} catch (_: Exception) {
}
updateChapter(event.checkpoint.index)
onEvent(
ReaderEvent.OnChangeProgress(
progress = calculateProgress(event.checkpoint.index),
firstVisibleItemIndex = event.checkpoint.index,
firstVisibleItemOffset = event.checkpoint.offset,
)
)
}
}
is ReaderEvent.OnLeave -> {
launch(Dispatchers.Main) {
_state.update { _state.update {
it.copy( it.copy(
lockMenu = true lockMenu = true
@ -282,7 +304,7 @@ class ReaderModel @Inject constructor(
} }
is ReaderEvent.OnOpenTranslator -> { is ReaderEvent.OnOpenTranslator -> {
launch { launch(Dispatchers.Default) {
val translatorIntent = Intent() val translatorIntent = Intent()
val browserIntent = Intent() val browserIntent = Intent()
@ -324,7 +346,7 @@ class ReaderModel @Inject constructor(
} }
is ReaderEvent.OnOpenShareApp -> { is ReaderEvent.OnOpenShareApp -> {
launch { launch(Dispatchers.Default) {
val shareIntent = Intent() val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND shareIntent.action = Intent.ACTION_SEND
@ -356,7 +378,7 @@ class ReaderModel @Inject constructor(
} }
is ReaderEvent.OnOpenWebBrowser -> { is ReaderEvent.OnOpenWebBrowser -> {
launch { launch(Dispatchers.Default) {
val browserIntent = Intent() val browserIntent = Intent()
browserIntent.action = Intent.ACTION_WEB_SEARCH browserIntent.action = Intent.ACTION_WEB_SEARCH
@ -382,7 +404,7 @@ class ReaderModel @Inject constructor(
} }
is ReaderEvent.OnOpenDictionary -> { is ReaderEvent.OnOpenDictionary -> {
launch { launch(Dispatchers.Default) {
val dictionaryIntent = Intent() val dictionaryIntent = Intent()
val browserIntent = Intent() val browserIntent = Intent()

View file

@ -416,7 +416,7 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
currentChapterProgress = state.value.currentChapterProgress, currentChapterProgress = state.value.currentChapterProgress,
isLoading = state.value.isLoading, isLoading = state.value.isLoading,
errorMessage = state.value.errorMessage, errorMessage = state.value.errorMessage,
checkpoint = state.value.checkpoint, checkpoints = state.value.checkpoints,
showMenu = state.value.showMenu, showMenu = state.value.showMenu,
lockMenu = state.value.lockMenu, lockMenu = state.value.lockMenu,
contentPadding = contentPadding, contentPadding = contentPadding,

View file

@ -29,7 +29,7 @@ data class ReaderState(
val isLoading: Boolean = true, val isLoading: Boolean = true,
val showMenu: Boolean = false, val showMenu: Boolean = false,
val checkpoint: Checkpoint = Checkpoint(0, 0), val checkpoints: List<Checkpoint> = emptyList(),
val lockMenu: Boolean = false, val lockMenu: Boolean = false,
val bottomSheet: BottomSheet? = null, val bottomSheet: BottomSheet? = null,

View file

@ -6,27 +6,37 @@
package ua.acclorite.book_story.ui.reader package ua.acclorite.book_story.ui.reader
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
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.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
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.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding 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.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
@ -48,34 +58,41 @@ fun ReaderBottomBar(
text: List<ReaderText>, text: List<ReaderText>,
listState: LazyListState, listState: LazyListState,
lockMenu: Boolean, lockMenu: Boolean,
checkpoint: Checkpoint, checkpoints: List<Checkpoint>,
bottomBarPadding: Dp, bottomBarPadding: Dp,
restoreCheckpoint: (ReaderEvent.OnRestoreCheckpoint) -> Unit, restoreCheckpoint: (ReaderEvent.OnRestoreCheckpoint) -> Unit,
scroll: (ReaderEvent.OnScroll) -> Unit, scroll: (ReaderEvent.OnScroll) -> Unit,
changeProgress: (ReaderEvent.OnChangeProgress) -> Unit changeProgress: (ReaderEvent.OnChangeProgress) -> Unit
) { ) {
val firstVisibleItemIndex = remember { val currentIndex by remember {
derivedStateOf { derivedStateOf {
listState.firstVisibleItemIndex listState.firstVisibleItemIndex
} }
} }
val arrowDirection = remember(checkpoint.index, firstVisibleItemIndex) { val checkpointsProgress = rememberCheckpointsProgress(
derivedStateOf { checkpoints = checkpoints,
val checkpointIndex = checkpoint.index text = text,
val index = firstVisibleItemIndex.value currentIndex = currentIndex
)
when { val currentCheckpoint = rememberCurrentCheckpoint(
checkpointIndex > index -> Direction.END checkpoints = checkpoints,
checkpointIndex < index -> Direction.START currentIndex = currentIndex
else -> Direction.NEUTRAL )
val checkpointDirection = rememberCheckpointDirection(
currentCheckpoint = currentCheckpoint,
currentIndex = currentIndex
)
val restoreCheckpoint = remember(currentCheckpoint) {
{
if (currentCheckpoint != null) {
restoreCheckpoint(
ReaderEvent.OnRestoreCheckpoint(
currentCheckpoint
)
)
} }
} }
} }
val checkpointProgress = remember(checkpoint.index, text.lastIndex) {
derivedStateOf {
(checkpoint.index / text.lastIndex.toFloat()) * 0.987f
}
}
Column( Column(
Modifier Modifier
@ -83,12 +100,11 @@ fun ReaderBottomBar(
.background(MaterialTheme.colorScheme.readerBarsColor) .background(MaterialTheme.colorScheme.readerBarsColor)
.noRippleClickable(onClick = {}) .noRippleClickable(onClick = {})
.navigationBarsPadding() .navigationBarsPadding()
.padding(horizontal = 18.dp), .padding(horizontal = 18.dp)
.padding(top = 16.dp, bottom = 8.dp + bottomBarPadding),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.spacedBy(6.dp)
) { ) {
Spacer(Modifier.height(16.dp))
StyledText( StyledText(
text = progress, text = progress,
style = MaterialTheme.typography.titleLarge.copy( style = MaterialTheme.typography.titleLarge.copy(
@ -96,57 +112,182 @@ fun ReaderBottomBar(
) )
) )
Spacer(Modifier.height(6.dp))
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
HorizontalExpandingTransition( ReaderBottomBarCheckpoints(
visible = arrowDirection.value == Direction.START, checkpointDirection = checkpointDirection,
startDirection = true startArrow = {
) { IconButton(
IconButton( icon = Icons.AutoMirrored.Default.ArrowBack,
icon = Icons.AutoMirrored.Default.ArrowBack, contentDescription = R.string.checkpoint_back_content_desc,
contentDescription = R.string.checkpoint_back_content_desc, modifier = Modifier.size(24.dp),
modifier = Modifier.size(24.dp), color = MaterialTheme.colorScheme.secondary,
color = MaterialTheme.colorScheme.secondary, disableOnClick = false
disableOnClick = false ) {
) { restoreCheckpoint()
restoreCheckpoint(ReaderEvent.OnRestoreCheckpoint) }
},
endArrow = {
IconButton(
icon = Icons.AutoMirrored.Default.ArrowForward,
contentDescription = R.string.checkpoint_forward_content_desc,
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.secondary,
disableOnClick = false
) {
restoreCheckpoint()
}
},
slider = {
ReaderBottomBarSlider(
book = book,
lockMenu = lockMenu,
listState = listState,
scroll = scroll,
changeProgress = changeProgress
)
},
indicator = {
ReaderBottomBarCheckpointsIndicator(
checkpointsProgress = checkpointsProgress
)
} }
} )
}
}
}
Box( @Composable
modifier = Modifier.weight(1f), private fun rememberCheckpointsProgress(
contentAlignment = Alignment.CenterStart checkpoints: List<Checkpoint>,
) { text: List<ReaderText>,
ReaderBottomBarSlider( currentIndex: Int
book = book, ): List<Float> {
lockMenu = lockMenu, return remember(checkpoints, text, currentIndex) {
listState = listState, if (text.isEmpty()) return@remember emptyList()
scroll = scroll,
changeProgress = changeProgress
)
if (arrowDirection.value != Direction.NEUTRAL) { val progressScale = 0.987f
ReaderBottomBarSliderIndicator(progress = checkpointProgress.value) checkpoints.mapNotNull { checkpoint ->
} if (checkpoint.index == currentIndex) return@mapNotNull null
} (checkpoint.index / text.lastIndex.toFloat()) * progressScale
}
}
}
HorizontalExpandingTransition( @Composable
visible = arrowDirection.value == Direction.END, private fun rememberCurrentCheckpoint(
startDirection = false checkpoints: List<Checkpoint>,
) { currentIndex: Int
IconButton( ): Checkpoint? {
icon = Icons.AutoMirrored.Default.ArrowForward, return remember(checkpoints, currentIndex) {
contentDescription = R.string.checkpoint_forward_content_desc, checkpoints.lastOrNull { checkpoint ->
modifier = Modifier.size(24.dp), when {
color = MaterialTheme.colorScheme.secondary, checkpoint.index > currentIndex -> true
disableOnClick = false checkpoint.index < currentIndex -> true
) { else -> false
restoreCheckpoint(ReaderEvent.OnRestoreCheckpoint)
}
} }
} }
}
}
Spacer(Modifier.height(8.dp + bottomBarPadding)) @Composable
private fun rememberCheckpointDirection(
currentCheckpoint: Checkpoint?,
currentIndex: Int
): Direction {
return remember(currentCheckpoint, currentIndex) {
when {
currentCheckpoint == null -> Direction.NEUTRAL
currentCheckpoint.index > currentIndex -> Direction.END
currentCheckpoint.index < currentIndex -> Direction.START
else -> Direction.NEUTRAL
}
}
}
@Composable
private fun RowScope.ReaderBottomBarCheckpoints(
checkpointDirection: Direction,
startArrow: @Composable () -> Unit,
endArrow: @Composable () -> Unit,
slider: @Composable () -> Unit,
indicator: @Composable () -> Unit
) {
HorizontalExpandingTransition(
visible = checkpointDirection == Direction.START,
startDirection = true
) {
startArrow()
}
Box(
modifier = Modifier.weight(1f),
contentAlignment = Alignment.CenterStart
) {
slider()
indicator()
}
HorizontalExpandingTransition(
visible = checkpointDirection == Direction.END,
startDirection = false
) {
endArrow()
}
}
@Composable
private fun ReaderBottomBarSlider(
book: Book,
lockMenu: Boolean,
listState: LazyListState,
scroll: (ReaderEvent.OnScroll) -> Unit,
changeProgress: (ReaderEvent.OnChangeProgress) -> Unit
) {
val interactionSource = remember { MutableInteractionSource() }
val isDragging = interactionSource.collectIsDraggedAsState()
val animatedProgress = animateFloatAsState(book.progress)
val progress = remember(isDragging.value, book.progress, animatedProgress.value) {
if (isDragging.value) book.progress
else animatedProgress.value
}
Slider(
value = progress,
enabled = !lockMenu,
onValueChange = {
if (listState.layoutInfo.totalItemsCount > 0) {
scroll(ReaderEvent.OnScroll(it))
changeProgress(
ReaderEvent.OnChangeProgress(
progress = it,
firstVisibleItemIndex = listState.firstVisibleItemIndex,
firstVisibleItemOffset = 0
)
)
}
},
interactionSource = interactionSource,
colors = SliderDefaults.colors(
inactiveTrackColor = MaterialTheme.colorScheme.secondary.copy(0.15f),
disabledActiveTrackColor = MaterialTheme.colorScheme.primary,
disabledThumbColor = MaterialTheme.colorScheme.primary,
disabledInactiveTrackColor = MaterialTheme.colorScheme.secondary.copy(0.15f),
)
)
}
@Composable
private fun ReaderBottomBarCheckpointsIndicator(checkpointsProgress: List<Float>) {
checkpointsProgress.forEach { checkpoint ->
Row {
Spacer(modifier = Modifier.fillMaxWidth(checkpoint))
Box(
Modifier
.width(4.dp)
.height(16.dp)
.clip(RoundedCornerShape(0.5.dp))
.background(MaterialTheme.colorScheme.onPrimary.copy(0.7f))
)
}
} }
} }

View file

@ -1,47 +0,0 @@
/*
* Book's Story free and open-source Material You eBook reader.
* Copyright (C) 2024-2025 Acclorite
* SPDX-License-Identifier: GPL-3.0-only
*/
package ua.acclorite.book_story.ui.reader
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import ua.acclorite.book_story.domain.model.library.Book
import ua.acclorite.book_story.presentation.reader.ReaderEvent
@Composable
fun ReaderBottomBarSlider(
book: Book,
lockMenu: Boolean,
listState: LazyListState,
scroll: (ReaderEvent.OnScroll) -> Unit,
changeProgress: (ReaderEvent.OnChangeProgress) -> Unit
) {
Slider(
value = book.progress,
enabled = !lockMenu,
onValueChange = {
if (listState.layoutInfo.totalItemsCount > 0) {
scroll(ReaderEvent.OnScroll(it))
changeProgress(
ReaderEvent.OnChangeProgress(
progress = it,
firstVisibleItemIndex = listState.firstVisibleItemIndex,
firstVisibleItemOffset = 0
)
)
}
},
colors = SliderDefaults.colors(
inactiveTrackColor = MaterialTheme.colorScheme.secondary.copy(0.15f),
disabledActiveTrackColor = MaterialTheme.colorScheme.primary,
disabledThumbColor = MaterialTheme.colorScheme.primary,
disabledInactiveTrackColor = MaterialTheme.colorScheme.secondary.copy(0.15f),
)
)
}

View file

@ -1,37 +0,0 @@
/*
* Book's Story free and open-source Material You eBook reader.
* Copyright (C) 2024-2025 Acclorite
* SPDX-License-Identifier: GPL-3.0-only
*/
package ua.acclorite.book_story.ui.reader
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
fun ReaderBottomBarSliderIndicator(progress: Float) {
Row(Modifier.fillMaxWidth()) {
Spacer(
modifier = Modifier.fillMaxWidth(progress)
)
Box(
Modifier
.width(4.dp)
.height(16.dp)
.clip(RoundedCornerShape(0.5.dp))
.background(MaterialTheme.colorScheme.onPrimary.copy(0.6f))
)
}
}

View file

@ -49,7 +49,7 @@ fun ReaderContent(
currentChapterProgress: Float, currentChapterProgress: Float,
isLoading: Boolean, isLoading: Boolean,
errorMessage: UIText?, errorMessage: UIText?,
checkpoint: Checkpoint, checkpoints: List<Checkpoint>,
showMenu: Boolean, showMenu: Boolean,
lockMenu: Boolean, lockMenu: Boolean,
contentPadding: PaddingValues, contentPadding: PaddingValues,
@ -128,7 +128,7 @@ fun ReaderContent(
perceptionExpanderThickness = perceptionExpanderThickness, perceptionExpanderThickness = perceptionExpanderThickness,
currentChapterProgress = currentChapterProgress, currentChapterProgress = currentChapterProgress,
isLoading = isLoading, isLoading = isLoading,
checkpoint = checkpoint, checkpoints = checkpoints,
showMenu = showMenu, showMenu = showMenu,
lockMenu = lockMenu, lockMenu = lockMenu,
contentPadding = contentPadding, contentPadding = contentPadding,

View file

@ -53,7 +53,7 @@ fun ReaderScaffold(
perceptionExpanderThickness: Dp, perceptionExpanderThickness: Dp,
currentChapterProgress: Float, currentChapterProgress: Float,
isLoading: Boolean, isLoading: Boolean,
checkpoint: Checkpoint, checkpoints: List<Checkpoint>,
showMenu: Boolean, showMenu: Boolean,
lockMenu: Boolean, lockMenu: Boolean,
contentPadding: PaddingValues, contentPadding: PaddingValues,
@ -150,7 +150,7 @@ fun ReaderScaffold(
text = text, text = text,
listState = listState, listState = listState,
lockMenu = lockMenu, lockMenu = lockMenu,
checkpoint = checkpoint, checkpoints = checkpoints,
bottomBarPadding = bottomBarPadding, bottomBarPadding = bottomBarPadding,
restoreCheckpoint = restoreCheckpoint, restoreCheckpoint = restoreCheckpoint,
scroll = scroll, scroll = scroll,