🚀 Progress bar in Reader

* Added compact progress bar that always shows current progress

Resolves: #148
This commit is contained in:
Acclorite 2025-01-11 20:58:29 +02:00
parent 8c2de20e88
commit 499e5dae7f
17 changed files with 231 additions and 78 deletions

View file

@ -9,7 +9,6 @@ import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@ -18,7 +17,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalTextToolbar
@ -295,7 +293,7 @@ fun SelectionContainer(
CompositionLocalProvider(
LocalTextToolbar provides selectionToolbar
) {
SelectionContainer(Modifier.fillMaxSize()) {
SelectionContainer {
content(isToolbarHidden.value)
}
}

View file

@ -53,6 +53,7 @@ object DataStoreConstants {
val IMAGES_ALIGNMENT = stringPreferencesKey("images_alignment")
val IMAGES_WIDTH = doublePreferencesKey("images_width")
val IMAGES_COLOR_EFFECTS = stringPreferencesKey("images_color_effects")
val PROGRESS_BAR = booleanPreferencesKey("progress_bar")
// Browse settings
val BROWSE_FILES_STRUCTURE = stringPreferencesKey("browse_files_structure")

View file

@ -28,10 +28,8 @@ import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.library.book.Book
import ua.acclorite.book_story.domain.reader.Checkpoint
import ua.acclorite.book_story.domain.reader.ReaderText
import ua.acclorite.book_story.domain.reader.ReaderText.Chapter
import ua.acclorite.book_story.domain.util.Direction
import ua.acclorite.book_story.presentation.core.components.common.IconButton
import ua.acclorite.book_story.presentation.core.util.calculateProgress
import ua.acclorite.book_story.presentation.core.util.noRippleClickable
import ua.acclorite.book_story.ui.reader.ReaderEvent
import ua.acclorite.book_story.ui.theme.Colors
@ -40,34 +38,16 @@ import ua.acclorite.book_story.ui.theme.HorizontalExpandingTransition
@Composable
fun ReaderBottomBar(
book: Book,
progress: String,
text: List<ReaderText>,
listState: LazyListState,
lockMenu: Boolean,
currentChapter: Chapter?,
currentChapterProgress: Float,
checkpoint: Checkpoint,
bottomBarPadding: Dp,
restoreCheckpoint: (ReaderEvent.OnRestoreCheckpoint) -> Unit,
scroll: (ReaderEvent.OnScroll) -> Unit,
changeProgress: (ReaderEvent.OnChangeProgress) -> Unit
) {
val bookProgress = remember(book.progress) {
derivedStateOf {
"${book.progress.calculateProgress(2)}%"
}
}
val chapterProgress = remember(currentChapter, currentChapterProgress) {
derivedStateOf {
if (currentChapter == null) return@derivedStateOf ""
" (${currentChapterProgress.calculateProgress(2)}%)"
}
}
val progress = remember(bookProgress.value, chapterProgress.value) {
derivedStateOf {
"${bookProgress.value}${chapterProgress.value}"
}
}
val arrowDirection = remember(checkpoint.index, listState.firstVisibleItemIndex) {
derivedStateOf {
val checkpointIndex = checkpoint.index
@ -99,7 +79,7 @@ fun ReaderBottomBar(
Spacer(Modifier.height(16.dp))
Text(
text = progress.value,
text = progress,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleLarge
)

View file

@ -52,6 +52,8 @@ fun ReaderContent(
horizontalGestureSensitivity: Dp,
highlightedReading: Boolean,
highlightedReadingThickness: FontWeight,
progress: String,
progressBar: Boolean,
paragraphHeight: Dp,
sidePadding: Dp,
bottomBarPadding: Dp,
@ -122,6 +124,8 @@ fun ReaderContent(
horizontalGestureSensitivity = horizontalGestureSensitivity,
highlightedReading = highlightedReading,
highlightedReadingThickness = highlightedReadingThickness,
progress = progress,
progressBar = progressBar,
paragraphHeight = paragraphHeight,
sidePadding = sidePadding,
bottomBarPadding = bottomBarPadding,

View file

@ -1,7 +1,12 @@
package ua.acclorite.book_story.presentation.reader
import android.os.Build
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
@ -27,6 +32,7 @@ import ua.acclorite.book_story.domain.reader.ReaderHorizontalGesture
import ua.acclorite.book_story.domain.reader.ReaderImagesAlignment
import ua.acclorite.book_story.domain.reader.ReaderText
import ua.acclorite.book_story.domain.reader.ReaderTextAlignment
import ua.acclorite.book_story.presentation.core.components.common.AnimatedVisibility
import ua.acclorite.book_story.presentation.core.components.common.LazyColumnWithScrollbar
import ua.acclorite.book_story.presentation.core.components.common.SelectionContainer
import ua.acclorite.book_story.presentation.core.components.common.SpacedItem
@ -46,6 +52,8 @@ fun ReaderLayout(
horizontalGestureSensitivity: Dp,
highlightedReading: Boolean,
highlightedReadingThickness: FontWeight,
progress: String,
progressBar: Boolean,
paragraphHeight: Dp,
sidePadding: Dp,
backgroundColor: Color,
@ -116,10 +124,8 @@ fun ReaderLayout(
)
}
) { toolbarHidden ->
LazyColumnWithScrollbar(
state = listState,
enableScrollbar = false,
modifier = Modifier
Column(
Modifier
.fillMaxSize()
.background(backgroundColor)
.then(
@ -146,7 +152,13 @@ fun ReaderLayout(
horizontalGestureScroll = horizontalGestureScroll,
horizontalGestureSensitivity = horizontalGestureSensitivity,
isLoading = isLoading
),
)
) {
LazyColumnWithScrollbar(
state = listState,
enableScrollbar = false,
parentModifier = Modifier.weight(1f),
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(
top = (WindowInsets.displayCutout.asPaddingValues()
.calculateTopPadding() + paragraphHeight)
@ -199,5 +211,17 @@ fun ReaderLayout(
}
}
}
AnimatedVisibility(
visible = !showMenu && progressBar,
enter = slideInVertically { it } + expandVertically(),
exit = slideOutVertically { it } + shrinkVertically()
) {
ReaderProgressBar(
progress = progress,
fontColor = fontColor
)
}
}
}
}

View file

@ -0,0 +1,36 @@
package ua.acclorite.book_story.presentation.reader
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.selection.DisableSelection
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@Composable
fun ReaderProgressBar(
progress: String,
fontColor: Color
) {
Box(
Modifier
.fillMaxWidth()
.padding(14.dp),
contentAlignment = Alignment.Center
) {
DisableSelection {
Text(
progress,
style = MaterialTheme.typography.bodyLarge,
color = fontColor,
overflow = TextOverflow.Ellipsis
)
}
}
}

View file

@ -56,6 +56,8 @@ fun ReaderScaffold(
horizontalGestureSensitivity: Dp,
highlightedReading: Boolean,
highlightedReadingThickness: FontWeight,
progress: String,
progressBar: Boolean,
paragraphHeight: Dp,
sidePadding: Dp,
bottomBarPadding: Dp,
@ -130,11 +132,10 @@ fun ReaderScaffold(
) {
ReaderBottomBar(
book = book,
progress = progress,
text = text,
listState = listState,
lockMenu = lockMenu,
currentChapter = currentChapter,
currentChapterProgress = currentChapterProgress,
checkpoint = checkpoint,
bottomBarPadding = bottomBarPadding,
restoreCheckpoint = restoreCheckpoint,
@ -154,6 +155,8 @@ fun ReaderScaffold(
horizontalGestureSensitivity = horizontalGestureSensitivity,
highlightedReading = highlightedReading,
highlightedReadingThickness = highlightedReadingThickness,
progress = progress,
progressBar = progressBar,
paragraphHeight = paragraphHeight,
sidePadding = sidePadding,
backgroundColor = backgroundColor,

View file

@ -28,6 +28,7 @@ import ua.acclorite.book_story.presentation.settings.reader.font.FontSubcategory
import ua.acclorite.book_story.presentation.settings.reader.images.ImagesSubcategory
import ua.acclorite.book_story.presentation.settings.reader.misc.MiscSubcategory
import ua.acclorite.book_story.presentation.settings.reader.padding.PaddingSubcategory
import ua.acclorite.book_story.presentation.settings.reader.progress.ProgressSubcategory
import ua.acclorite.book_story.presentation.settings.reader.reading_mode.ReadingModeSubcategory
import ua.acclorite.book_story.presentation.settings.reader.reading_speed.ReadingSpeedSubcategory
import ua.acclorite.book_story.presentation.settings.reader.system.SystemSubcategory
@ -128,6 +129,9 @@ fun ReaderSettingsBottomSheet(
ChaptersSubcategory(
titleColor = { MaterialTheme.colorScheme.onSurface }
)
ProgressSubcategory(
titleColor = { MaterialTheme.colorScheme.onSurface }
)
TranslatorSubcategory(
titleColor = { MaterialTheme.colorScheme.onSurface },
showDivider = false

View file

@ -11,6 +11,7 @@ import ua.acclorite.book_story.presentation.settings.reader.font.FontSubcategory
import ua.acclorite.book_story.presentation.settings.reader.images.ImagesSubcategory
import ua.acclorite.book_story.presentation.settings.reader.misc.MiscSubcategory
import ua.acclorite.book_story.presentation.settings.reader.padding.PaddingSubcategory
import ua.acclorite.book_story.presentation.settings.reader.progress.ProgressSubcategory
import ua.acclorite.book_story.presentation.settings.reader.reading_mode.ReadingModeSubcategory
import ua.acclorite.book_story.presentation.settings.reader.reading_speed.ReadingSpeedSubcategory
import ua.acclorite.book_story.presentation.settings.reader.system.SystemSubcategory
@ -44,6 +45,9 @@ fun LazyListScope.ReaderSettingsCategory(
ReadingSpeedSubcategory(
titleColor = titleColor
)
ProgressSubcategory(
titleColor = titleColor
)
TranslatorSubcategory(
titleColor = titleColor
)

View file

@ -0,0 +1,30 @@
@file:Suppress("FunctionName")
package ua.acclorite.book_story.presentation.settings.reader.progress
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.settings.components.SettingsSubcategory
import ua.acclorite.book_story.presentation.settings.reader.progress.components.ProgressBarOption
fun LazyListScope.ProgressSubcategory(
titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary },
title: @Composable () -> String = { stringResource(id = R.string.progress_reader_settings) },
showTitle: Boolean = true,
showDivider: Boolean = true
) {
SettingsSubcategory(
titleColor = titleColor,
title = title,
showTitle = showTitle,
showDivider = showDivider,
) {
item {
ProgressBarOption()
}
}
}

View file

@ -0,0 +1,28 @@
package ua.acclorite.book_story.presentation.settings.reader.progress.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ua.acclorite.book_story.R
import ua.acclorite.book_story.presentation.core.components.settings.SwitchWithTitle
import ua.acclorite.book_story.ui.main.MainEvent
import ua.acclorite.book_story.ui.main.MainModel
@Composable
fun ProgressBarOption() {
val mainModel = hiltViewModel<MainModel>()
val state = mainModel.state.collectAsStateWithLifecycle()
SwitchWithTitle(
selected = state.value.progressBar,
title = stringResource(id = R.string.progress_bar_option),
onClick = {
mainModel.onEvent(
MainEvent.OnChangeProgressBar(
!state.value.progressBar
)
)
}
)
}

View file

@ -55,4 +55,5 @@ sealed class MainEvent {
data class OnChangeImagesAlignment(val value: String) : MainEvent()
data class OnChangeImagesWidth(val value: Float) : MainEvent()
data class OnChangeImagesColorEffects(val value: String) : MainEvent()
data class OnChangeProgressBar(val value: Boolean) : MainEvent()
}

View file

@ -464,6 +464,14 @@ class MainModel @Inject constructor(
it.copy(imagesColorEffects = this.toColorEffects())
}
)
is MainEvent.OnChangeProgressBar -> handleDatastoreUpdate(
key = DataStoreConstants.PROGRESS_BAR,
value = event.value,
updateState = {
it.copy(progressBar = this)
}
)
}
}

View file

@ -102,6 +102,7 @@ data class MainState(
val imagesAlignment: ReaderImagesAlignment = provideDefaultValue { ReaderImagesAlignment.START },
val imagesWidth: Float = provideDefaultValue { 0.8f },
val imagesColorEffects: ReaderColorEffects = provideDefaultValue { ReaderColorEffects.OFF },
val progressBar: Boolean = provideDefaultValue { false },
// Browse Settings
val browseFilesStructure: BrowseFilesStructure = provideDefaultValue {
@ -339,6 +340,10 @@ data class MainState(
imagesColorEffects = provideValue(
IMAGES_COLOR_EFFECTS, convert = { toColorEffects() }
) { imagesColorEffects },
progressBar = provideValue(
PROGRESS_BAR
) { progressBar },
)
}
}

View file

@ -44,6 +44,7 @@ import ua.acclorite.book_story.domain.reader.ReaderTextAlignment
import ua.acclorite.book_story.presentation.core.constants.Constants
import ua.acclorite.book_story.presentation.core.constants.provideFonts
import ua.acclorite.book_story.presentation.core.util.LocalActivity
import ua.acclorite.book_story.presentation.core.util.calculateProgress
import ua.acclorite.book_story.presentation.core.util.setBrightness
import ua.acclorite.book_story.presentation.navigator.LocalNavigator
import ua.acclorite.book_story.presentation.reader.ReaderContent
@ -280,6 +281,26 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
(mainState.value.bottomBarPadding * 4f).dp
}
val bookProgress = remember(state.value.book.progress) {
derivedStateOf {
"${state.value.book.progress.calculateProgress(2)}%"
}
}
val chapterProgress = remember(
state.value.currentChapter,
state.value.currentChapterProgress
) {
derivedStateOf {
if (state.value.currentChapter == null) return@derivedStateOf ""
" (${state.value.currentChapterProgress.calculateProgress(2)}%)"
}
}
val progress = remember(bookProgress.value, chapterProgress.value) {
derivedStateOf {
"${bookProgress.value}${chapterProgress.value}"
}
}
LaunchedEffect(Unit) {
screenModel.init(
bookId = bookId,
@ -368,6 +389,8 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
horizontalGestureSensitivity = horizontalGestureSensitivity,
highlightedReading = mainState.value.highlightedReading,
highlightedReadingThickness = highlightedReadingThickness,
progress = progress.value,
progressBar = mainState.value.progressBar,
paragraphHeight = paragraphHeight,
sidePadding = sidePadding,
bottomBarPadding = bottomBarPadding,

View file

@ -138,6 +138,7 @@
<string name="system_reader_settings">Система</string>
<string name="chapters_reader_settings">Розділи</string>
<string name="images_reader_settings">Зображення</string>
<string name="progress_reader_settings">Прогрес</string>
<string name="theme_appearance_settings">Налаштування теми</string>
<string name="colors_appearance_settings">Кольори</string>
<string name="general_browse_settings">Головні</string>
@ -203,6 +204,7 @@
<string name="images_alignment_option">Розташування</string>
<string name="images_width_option">Розмір</string>
<string name="images_color_effects_option">Ефекти кольору</string>
<string name="progress_bar_option">Панель прогресу</string>
<!-- Browse -->
<string name="browse_files_structure_option">Структура файлів</string>

View file

@ -191,6 +191,7 @@
<string name="system_reader_settings">System</string>
<string name="chapters_reader_settings">Chapters</string>
<string name="images_reader_settings">Images</string>
<string name="progress_reader_settings">Progress</string>
<string name="theme_appearance_settings">Theme preferences</string>
<string name="colors_appearance_settings">Colors</string>
<string name="general_browse_settings">General</string>
@ -258,6 +259,7 @@
<string name="images_alignment_option">Alignment</string>
<string name="images_width_option">Size</string>
<string name="images_color_effects_option">Color effects</string>
<string name="progress_bar_option">Progress bar</string>
<!-- Browse -->
<string name="browse_files_structure_option">Files structure</string>