feat: multiple checkpoints
This commit is contained in:
parent
57afade51a
commit
754a0d7cf3
9 changed files with 273 additions and 191 deletions
|
|
@ -9,6 +9,7 @@ package ua.acclorite.book_story.presentation.reader
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.Immutable
|
||||
import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter
|
||||
import ua.acclorite.book_story.presentation.reader.model.Checkpoint
|
||||
|
||||
@Immutable
|
||||
sealed class ReaderEvent {
|
||||
|
|
@ -39,7 +40,9 @@ sealed class ReaderEvent {
|
|||
val progress: Float
|
||||
) : ReaderEvent()
|
||||
|
||||
data object OnRestoreCheckpoint : ReaderEvent()
|
||||
data class OnRestoreCheckpoint(
|
||||
val checkpoint: Checkpoint
|
||||
) : ReaderEvent()
|
||||
|
||||
data class OnLeave(
|
||||
val activity: ComponentActivity,
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ class ReaderModel @Inject constructor(
|
|||
}
|
||||
|
||||
is ReaderEvent.OnMenuVisibility -> {
|
||||
launch {
|
||||
launch(Dispatchers.Default) {
|
||||
if (_state.value.lockMenu) return@launch
|
||||
|
||||
yield()
|
||||
|
|
@ -152,21 +152,31 @@ class ReaderModel @Inject constructor(
|
|||
show = event.show || !event.fullscreenMode,
|
||||
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 {
|
||||
it.copy(
|
||||
showMenu = event.show,
|
||||
checkpoint = _state.value.listState.run {
|
||||
if (!event.show || !event.saveCheckpoint) return@run it.checkpoint
|
||||
|
||||
Checkpoint(firstVisibleItemIndex, firstVisibleItemScrollOffset)
|
||||
}
|
||||
checkpoints = checkpoints
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is ReaderEvent.OnChangeProgress -> {
|
||||
launch(Dispatchers.IO) {
|
||||
launch(Dispatchers.Default) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
book = it.book.copy(
|
||||
|
|
@ -185,7 +195,7 @@ class ReaderModel @Inject constructor(
|
|||
}
|
||||
|
||||
is ReaderEvent.OnScrollToChapter -> {
|
||||
launch {
|
||||
launch(Dispatchers.Default) {
|
||||
_state.value.apply {
|
||||
val chapterIndex = text.indexOf(event.chapter).takeIf { it != -1 }
|
||||
if (chapterIndex == null) {
|
||||
|
|
@ -207,40 +217,52 @@ class ReaderModel @Inject constructor(
|
|||
|
||||
is ReaderEvent.OnScroll -> {
|
||||
scrollJob?.cancel()
|
||||
scrollJob = launch {
|
||||
scrollJob = launch(Dispatchers.Main) {
|
||||
delay(300)
|
||||
yield()
|
||||
|
||||
val scrollTo = (_state.value.text.lastIndex * event.progress).roundToInt()
|
||||
_state.value.listState.requestScrollToItem(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 -> {
|
||||
launch {
|
||||
yield()
|
||||
is ReaderEvent.OnRestoreCheckpoint -> {
|
||||
launch(Dispatchers.Default) {
|
||||
_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 {
|
||||
it.copy(
|
||||
lockMenu = true
|
||||
|
|
@ -282,7 +304,7 @@ class ReaderModel @Inject constructor(
|
|||
}
|
||||
|
||||
is ReaderEvent.OnOpenTranslator -> {
|
||||
launch {
|
||||
launch(Dispatchers.Default) {
|
||||
val translatorIntent = Intent()
|
||||
val browserIntent = Intent()
|
||||
|
||||
|
|
@ -324,7 +346,7 @@ class ReaderModel @Inject constructor(
|
|||
}
|
||||
|
||||
is ReaderEvent.OnOpenShareApp -> {
|
||||
launch {
|
||||
launch(Dispatchers.Default) {
|
||||
val shareIntent = Intent()
|
||||
|
||||
shareIntent.action = Intent.ACTION_SEND
|
||||
|
|
@ -356,7 +378,7 @@ class ReaderModel @Inject constructor(
|
|||
}
|
||||
|
||||
is ReaderEvent.OnOpenWebBrowser -> {
|
||||
launch {
|
||||
launch(Dispatchers.Default) {
|
||||
val browserIntent = Intent()
|
||||
|
||||
browserIntent.action = Intent.ACTION_WEB_SEARCH
|
||||
|
|
@ -382,7 +404,7 @@ class ReaderModel @Inject constructor(
|
|||
}
|
||||
|
||||
is ReaderEvent.OnOpenDictionary -> {
|
||||
launch {
|
||||
launch(Dispatchers.Default) {
|
||||
val dictionaryIntent = Intent()
|
||||
val browserIntent = Intent()
|
||||
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
|
|||
currentChapterProgress = state.value.currentChapterProgress,
|
||||
isLoading = state.value.isLoading,
|
||||
errorMessage = state.value.errorMessage,
|
||||
checkpoint = state.value.checkpoint,
|
||||
checkpoints = state.value.checkpoints,
|
||||
showMenu = state.value.showMenu,
|
||||
lockMenu = state.value.lockMenu,
|
||||
contentPadding = contentPadding,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ data class ReaderState(
|
|||
val isLoading: Boolean = true,
|
||||
|
||||
val showMenu: Boolean = false,
|
||||
val checkpoint: Checkpoint = Checkpoint(0, 0),
|
||||
val checkpoints: List<Checkpoint> = emptyList(),
|
||||
val lockMenu: Boolean = false,
|
||||
|
||||
val bottomSheet: BottomSheet? = null,
|
||||
|
|
|
|||
|
|
@ -6,27 +6,37 @@
|
|||
|
||||
package ua.acclorite.book_story.ui.reader
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
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.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ua.acclorite.book_story.R
|
||||
|
|
@ -48,34 +58,41 @@ fun ReaderBottomBar(
|
|||
text: List<ReaderText>,
|
||||
listState: LazyListState,
|
||||
lockMenu: Boolean,
|
||||
checkpoint: Checkpoint,
|
||||
checkpoints: List<Checkpoint>,
|
||||
bottomBarPadding: Dp,
|
||||
restoreCheckpoint: (ReaderEvent.OnRestoreCheckpoint) -> Unit,
|
||||
scroll: (ReaderEvent.OnScroll) -> Unit,
|
||||
changeProgress: (ReaderEvent.OnChangeProgress) -> Unit
|
||||
) {
|
||||
val firstVisibleItemIndex = remember {
|
||||
val currentIndex by remember {
|
||||
derivedStateOf {
|
||||
listState.firstVisibleItemIndex
|
||||
}
|
||||
}
|
||||
val arrowDirection = remember(checkpoint.index, firstVisibleItemIndex) {
|
||||
derivedStateOf {
|
||||
val checkpointIndex = checkpoint.index
|
||||
val index = firstVisibleItemIndex.value
|
||||
|
||||
when {
|
||||
checkpointIndex > index -> Direction.END
|
||||
checkpointIndex < index -> Direction.START
|
||||
else -> Direction.NEUTRAL
|
||||
val checkpointsProgress = rememberCheckpointsProgress(
|
||||
checkpoints = checkpoints,
|
||||
text = text,
|
||||
currentIndex = currentIndex
|
||||
)
|
||||
val currentCheckpoint = rememberCurrentCheckpoint(
|
||||
checkpoints = checkpoints,
|
||||
currentIndex = currentIndex
|
||||
)
|
||||
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(
|
||||
Modifier
|
||||
|
|
@ -83,12 +100,11 @@ fun ReaderBottomBar(
|
|||
.background(MaterialTheme.colorScheme.readerBarsColor)
|
||||
.noRippleClickable(onClick = {})
|
||||
.navigationBarsPadding()
|
||||
.padding(horizontal = 18.dp),
|
||||
.padding(horizontal = 18.dp)
|
||||
.padding(top = 16.dp, bottom = 8.dp + bottomBarPadding),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
StyledText(
|
||||
text = progress,
|
||||
style = MaterialTheme.typography.titleLarge.copy(
|
||||
|
|
@ -96,57 +112,182 @@ fun ReaderBottomBar(
|
|||
)
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(6.dp))
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
HorizontalExpandingTransition(
|
||||
visible = arrowDirection.value == Direction.START,
|
||||
startDirection = true
|
||||
) {
|
||||
IconButton(
|
||||
icon = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = R.string.checkpoint_back_content_desc,
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
disableOnClick = false
|
||||
) {
|
||||
restoreCheckpoint(ReaderEvent.OnRestoreCheckpoint)
|
||||
ReaderBottomBarCheckpoints(
|
||||
checkpointDirection = checkpointDirection,
|
||||
startArrow = {
|
||||
IconButton(
|
||||
icon = Icons.AutoMirrored.Default.ArrowBack,
|
||||
contentDescription = R.string.checkpoint_back_content_desc,
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
disableOnClick = false
|
||||
) {
|
||||
restoreCheckpoint()
|
||||
}
|
||||
},
|
||||
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(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
ReaderBottomBarSlider(
|
||||
book = book,
|
||||
lockMenu = lockMenu,
|
||||
listState = listState,
|
||||
scroll = scroll,
|
||||
changeProgress = changeProgress
|
||||
)
|
||||
|
||||
if (arrowDirection.value != Direction.NEUTRAL) {
|
||||
ReaderBottomBarSliderIndicator(progress = checkpointProgress.value)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalExpandingTransition(
|
||||
visible = arrowDirection.value == Direction.END,
|
||||
startDirection = false
|
||||
) {
|
||||
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(ReaderEvent.OnRestoreCheckpoint)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberCheckpointsProgress(
|
||||
checkpoints: List<Checkpoint>,
|
||||
text: List<ReaderText>,
|
||||
currentIndex: Int
|
||||
): List<Float> {
|
||||
return remember(checkpoints, text, currentIndex) {
|
||||
if (text.isEmpty()) return@remember emptyList()
|
||||
|
||||
val progressScale = 0.987f
|
||||
checkpoints.mapNotNull { checkpoint ->
|
||||
if (checkpoint.index == currentIndex) return@mapNotNull null
|
||||
(checkpoint.index / text.lastIndex.toFloat()) * progressScale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberCurrentCheckpoint(
|
||||
checkpoints: List<Checkpoint>,
|
||||
currentIndex: Int
|
||||
): Checkpoint? {
|
||||
return remember(checkpoints, currentIndex) {
|
||||
checkpoints.lastOrNull { checkpoint ->
|
||||
when {
|
||||
checkpoint.index > currentIndex -> true
|
||||
checkpoint.index < currentIndex -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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))
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(8.dp + bottomBarPadding))
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ fun ReaderContent(
|
|||
currentChapterProgress: Float,
|
||||
isLoading: Boolean,
|
||||
errorMessage: UIText?,
|
||||
checkpoint: Checkpoint,
|
||||
checkpoints: List<Checkpoint>,
|
||||
showMenu: Boolean,
|
||||
lockMenu: Boolean,
|
||||
contentPadding: PaddingValues,
|
||||
|
|
@ -128,7 +128,7 @@ fun ReaderContent(
|
|||
perceptionExpanderThickness = perceptionExpanderThickness,
|
||||
currentChapterProgress = currentChapterProgress,
|
||||
isLoading = isLoading,
|
||||
checkpoint = checkpoint,
|
||||
checkpoints = checkpoints,
|
||||
showMenu = showMenu,
|
||||
lockMenu = lockMenu,
|
||||
contentPadding = contentPadding,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ fun ReaderScaffold(
|
|||
perceptionExpanderThickness: Dp,
|
||||
currentChapterProgress: Float,
|
||||
isLoading: Boolean,
|
||||
checkpoint: Checkpoint,
|
||||
checkpoints: List<Checkpoint>,
|
||||
showMenu: Boolean,
|
||||
lockMenu: Boolean,
|
||||
contentPadding: PaddingValues,
|
||||
|
|
@ -150,7 +150,7 @@ fun ReaderScaffold(
|
|||
text = text,
|
||||
listState = listState,
|
||||
lockMenu = lockMenu,
|
||||
checkpoint = checkpoint,
|
||||
checkpoints = checkpoints,
|
||||
bottomBarPadding = bottomBarPadding,
|
||||
restoreCheckpoint = restoreCheckpoint,
|
||||
scroll = scroll,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue