From ad41bc9a1a5ed1154ef51519f37980dc5be7b295 Mon Sep 17 00:00:00 2001 From: Acclorite <140836141+Acclorite@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:49:32 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Progress=20Count=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Option to change Progress Count method (Percentage/Quantity) * Improved progress calculation --- .../domain/reader/ReaderProgressCount.kt | 13 +++++ .../core/constants/DataStoreConstants.kt | 1 + .../reader/progress/ProgressSubcategory.kt | 5 ++ .../components/ProgressCountOption.kt | 41 ++++++++++++++++ .../acclorite/book_story/ui/main/MainEvent.kt | 1 + .../acclorite/book_story/ui/main/MainModel.kt | 9 ++++ .../acclorite/book_story/ui/main/MainState.kt | 7 +++ .../book_story/ui/reader/ReaderModel.kt | 29 ++++++++--- .../book_story/ui/reader/ReaderScreen.kt | 48 ++++++++++++++----- app/src/main/res/values-uk/strings.xml | 5 ++ app/src/main/res/values/strings.xml | 5 ++ 11 files changed, 145 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/reader/ReaderProgressCount.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/settings/reader/progress/components/ProgressCountOption.kt diff --git a/app/src/main/java/ua/acclorite/book_story/domain/reader/ReaderProgressCount.kt b/app/src/main/java/ua/acclorite/book_story/domain/reader/ReaderProgressCount.kt new file mode 100644 index 00000000..4abecd36 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/reader/ReaderProgressCount.kt @@ -0,0 +1,13 @@ +package ua.acclorite.book_story.domain.reader + +import androidx.compose.runtime.Immutable + +@Immutable +enum class ReaderProgressCount { + PERCENTAGE, + QUANTITY +} + +fun String.toProgressCount(): ReaderProgressCount { + return ReaderProgressCount.valueOf(this) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/core/constants/DataStoreConstants.kt b/app/src/main/java/ua/acclorite/book_story/presentation/core/constants/DataStoreConstants.kt index bd797ccd..253bafa3 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/core/constants/DataStoreConstants.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/core/constants/DataStoreConstants.kt @@ -58,6 +58,7 @@ object DataStoreConstants { val PROGRESS_BAR_PADDING = intPreferencesKey("progress_bar_padding") val PROGRESS_BAR_ALIGNMENT = stringPreferencesKey("progress_bar_alignment") val PROGRESS_BAR_FONT_SIZE = intPreferencesKey("progress_bar_font_size") + val PROGRESS_COUNT = stringPreferencesKey("progress_count") // Browse settings val BROWSE_LAYOUT = stringPreferencesKey("browse_layout") diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/settings/reader/progress/ProgressSubcategory.kt b/app/src/main/java/ua/acclorite/book_story/presentation/settings/reader/progress/ProgressSubcategory.kt index 12ee24e6..4114f4f0 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/settings/reader/progress/ProgressSubcategory.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/settings/reader/progress/ProgressSubcategory.kt @@ -13,6 +13,7 @@ import ua.acclorite.book_story.presentation.settings.reader.progress.components. import ua.acclorite.book_story.presentation.settings.reader.progress.components.ProgressBarFontSizeOption import ua.acclorite.book_story.presentation.settings.reader.progress.components.ProgressBarOption import ua.acclorite.book_story.presentation.settings.reader.progress.components.ProgressBarPaddingOption +import ua.acclorite.book_story.presentation.settings.reader.progress.components.ProgressCountOption fun LazyListScope.ProgressSubcategory( titleColor: @Composable () -> Color = { MaterialTheme.colorScheme.primary }, @@ -26,6 +27,10 @@ fun LazyListScope.ProgressSubcategory( showTitle = showTitle, showDivider = showDivider, ) { + item { + ProgressCountOption() + } + item { ProgressBarOption() } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/settings/reader/progress/components/ProgressCountOption.kt b/app/src/main/java/ua/acclorite/book_story/presentation/settings/reader/progress/components/ProgressCountOption.kt new file mode 100644 index 00000000..71a74e3a --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/settings/reader/progress/components/ProgressCountOption.kt @@ -0,0 +1,41 @@ +package ua.acclorite.book_story.presentation.settings.reader.progress.components + +import androidx.compose.material3.MaterialTheme +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.domain.reader.ReaderProgressCount +import ua.acclorite.book_story.domain.ui.ButtonItem +import ua.acclorite.book_story.presentation.core.components.settings.SegmentedButtonWithTitle +import ua.acclorite.book_story.ui.main.MainEvent +import ua.acclorite.book_story.ui.main.MainModel + +@Composable +fun ProgressCountOption() { + val mainModel = hiltViewModel() + val state = mainModel.state.collectAsStateWithLifecycle() + + SegmentedButtonWithTitle( + title = stringResource(id = R.string.progress_count_option), + buttons = ReaderProgressCount.entries.map { + ButtonItem( + id = it.toString(), + title = when (it) { + ReaderProgressCount.PERCENTAGE -> stringResource(id = R.string.progress_count_percentage) + ReaderProgressCount.QUANTITY -> stringResource(id = R.string.progress_count_quantity) + }, + textStyle = MaterialTheme.typography.labelLarge, + selected = it == state.value.progressCount + ) + }, + onClick = { + mainModel.onEvent( + MainEvent.OnChangeProgressCount( + it.id + ) + ) + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/main/MainEvent.kt b/app/src/main/java/ua/acclorite/book_story/ui/main/MainEvent.kt index 3a4b9bbf..b4c31081 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/main/MainEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/main/MainEvent.kt @@ -59,4 +59,5 @@ sealed class MainEvent { data class OnChangeProgressBarFontSize(val value: Int) : MainEvent() data class OnChangeBrowsePinnedPaths(val value: String) : MainEvent() data class OnChangeFontThickness(val value: String) : MainEvent() + data class OnChangeProgressCount(val value: String) : MainEvent() } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/main/MainModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/main/MainModel.kt index fde5f0b1..9494644e 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/main/MainModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/main/MainModel.kt @@ -21,6 +21,7 @@ import ua.acclorite.book_story.domain.browse.toBrowseSortOrder import ua.acclorite.book_story.domain.reader.toColorEffects import ua.acclorite.book_story.domain.reader.toFontThickness import ua.acclorite.book_story.domain.reader.toHorizontalGesture +import ua.acclorite.book_story.domain.reader.toProgressCount import ua.acclorite.book_story.domain.reader.toReaderScreenOrientation import ua.acclorite.book_story.domain.reader.toTextAlignment import ua.acclorite.book_story.domain.use_case.data_store.ChangeLanguage @@ -494,6 +495,14 @@ class MainModel @Inject constructor( it.copy(fontThickness = this.toFontThickness()) } ) + + is MainEvent.OnChangeProgressCount -> handleDatastoreUpdate( + key = DataStoreConstants.PROGRESS_COUNT, + value = event.value, + updateState = { + it.copy(progressCount = this.toProgressCount()) + } + ) } } diff --git a/app/src/main/java/ua/acclorite/book_story/ui/main/MainState.kt b/app/src/main/java/ua/acclorite/book_story/ui/main/MainState.kt index 30ff472a..6b4aa9e4 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/main/MainState.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/main/MainState.kt @@ -14,11 +14,13 @@ import ua.acclorite.book_story.domain.browse.toBrowseSortOrder import ua.acclorite.book_story.domain.reader.ReaderColorEffects import ua.acclorite.book_story.domain.reader.ReaderFontThickness import ua.acclorite.book_story.domain.reader.ReaderHorizontalGesture +import ua.acclorite.book_story.domain.reader.ReaderProgressCount import ua.acclorite.book_story.domain.reader.ReaderScreenOrientation import ua.acclorite.book_story.domain.reader.ReaderTextAlignment import ua.acclorite.book_story.domain.reader.toColorEffects import ua.acclorite.book_story.domain.reader.toFontThickness import ua.acclorite.book_story.domain.reader.toHorizontalGesture +import ua.acclorite.book_story.domain.reader.toProgressCount import ua.acclorite.book_story.domain.reader.toReaderScreenOrientation import ua.acclorite.book_story.domain.reader.toTextAlignment import ua.acclorite.book_story.domain.util.HorizontalAlignment @@ -107,6 +109,7 @@ data class MainState( val progressBarPadding: Int = provideDefaultValue { 4 }, val progressBarAlignment: HorizontalAlignment = provideDefaultValue { HorizontalAlignment.CENTER }, val progressBarFontSize: Int = provideDefaultValue { 8 }, + val progressCount: ReaderProgressCount = provideDefaultValue { ReaderProgressCount.PERCENTAGE }, // Browse Settings val browseLayout: BrowseLayout = provideDefaultValue { BrowseLayout.LIST }, @@ -357,6 +360,10 @@ data class MainState( fontThickness = provideValue( FONT_THICKNESS, convert = { toFontThickness() } ) { fontThickness }, + + progressCount = provideValue( + PROGRESS_COUNT, convert = { toProgressCount() } + ) { progressCount }, ) } } diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt index 13562f20..6dfee1d2 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt @@ -179,7 +179,6 @@ class ReaderModel @Inject constructor( launch { _state.value.apply { val chapterIndex = text.indexOf(event.chapter).takeIf { it != -1 } - if (chapterIndex == null) { return@launch } @@ -203,8 +202,9 @@ class ReaderModel @Inject constructor( delay(300) yield() - val scrollTo = (_state.value.text.size * event.progress).roundToInt() + val scrollTo = (_state.value.text.lastIndex * event.progress).roundToInt() _state.value.listState.requestScrollToItem(scrollTo) + updateChapter(scrollTo) } } @@ -489,7 +489,7 @@ class ReaderModel @Inject constructor( fun updateProgress(listState: LazyListState) { viewModelScope.launch(Dispatchers.Main) { snapshotFlow { - listState.run { firstVisibleItemIndex to firstVisibleItemScrollOffset } + listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }.distinctUntilChanged().debounce(300).collectLatest { (index, offset) -> val progress = calculateProgress(index) if (progress == _state.value.book.progress) return@collectLatest @@ -519,6 +519,21 @@ class ReaderModel @Inject constructor( } } + fun findChapterIndexAndLength(index: Int): Pair { + return findCurrentChapter(index)?.let { chapter -> + _state.value.text.run { + val startIndex = indexOf(chapter).coerceIn(0, lastIndex) + val endIndex = (indexOfFirst { + it is Chapter && indexOf(it) > startIndex + }.takeIf { it != -1 }) ?: (lastIndex + 1) + + val currentIndexInChapter = (index - startIndex).coerceAtLeast(1) + val chapterLength = endIndex - (startIndex + 1) + currentIndexInChapter to chapterLength + } + } ?: (-1 to -1) + } + private fun updateChapter(index: Int) { viewModelScope.launch { val (currentChapter, currentChapterProgress) = calculateCurrentChapter(index) @@ -540,13 +555,13 @@ class ReaderModel @Inject constructor( val currentChapter = findCurrentChapter(index) val currentChapterProgress = currentChapter?.let { chapter -> _state.value.text.run { - val startIndex = (indexOf(chapter) + 1).coerceAtMost(count()) + val startIndex = indexOf(chapter).coerceIn(0, lastIndex) val endIndex = (indexOfFirst { it is Chapter && indexOf(it) > startIndex - }.takeIf { it != -1 }?.minus(1)) ?: lastIndex + }.takeIf { it != -1 }) ?: (lastIndex + 1) - val currentIndexInChapter = index - startIndex - val chapterLength = endIndex - startIndex + val currentIndexInChapter = (index - startIndex).coerceAtLeast(1) + val chapterLength = endIndex - (startIndex + 1) (currentIndexInChapter / chapterLength.toFloat()) } }.coerceAndPreventNaN() diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScreen.kt index 795572fd..e95b8c0e 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScreen.kt @@ -40,6 +40,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.parcelize.Parcelize import ua.acclorite.book_story.domain.navigator.Screen import ua.acclorite.book_story.domain.reader.ReaderColorEffects +import ua.acclorite.book_story.domain.reader.ReaderProgressCount 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 @@ -51,6 +52,7 @@ import ua.acclorite.book_story.presentation.reader.ReaderContent import ua.acclorite.book_story.ui.book_info.BookInfoScreen import ua.acclorite.book_story.ui.main.MainModel import ua.acclorite.book_story.ui.settings.SettingsModel +import kotlin.math.roundToInt @Parcelize data class ReaderScreen(val bookId: Int) : Screen, Parcelable { @@ -289,24 +291,46 @@ 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 bookProgress = remember( + state.value.book.progress, + state.value.text, + mainState.value.progressCount + ) { + when (mainState.value.progressCount) { + ReaderProgressCount.PERCENTAGE -> { + "${state.value.book.progress.calculateProgress(2)}%" + } + + ReaderProgressCount.QUANTITY -> { + val index = + (state.value.book.progress * state.value.text.lastIndex + 1).roundToInt() + "$index / ${state.value.text.size}" + } } } val chapterProgress = remember( + state.value.text, + state.value.book.progress, state.value.currentChapter, - state.value.currentChapterProgress + state.value.currentChapterProgress, + mainState.value.progressCount ) { - derivedStateOf { - if (state.value.currentChapter == null) return@derivedStateOf "" - " (${state.value.currentChapterProgress.calculateProgress(2)}%)" + if (state.value.currentChapter == null) return@remember "" + when (mainState.value.progressCount) { + ReaderProgressCount.PERCENTAGE -> { + " (${state.value.currentChapterProgress.calculateProgress(2)}%)" + } + + ReaderProgressCount.QUANTITY -> { + val (index, length) = screenModel.findChapterIndexAndLength( + (state.value.book.progress * state.value.text.lastIndex).roundToInt() + ).apply { if (first == -1 && second == -1) return@remember "" } + " (${index} / ${length})" + } } } - val progress = remember(bookProgress.value, chapterProgress.value) { - derivedStateOf { - "${bookProgress.value}${chapterProgress.value}" - } + val progress = remember(bookProgress, chapterProgress) { + "${bookProgress}${chapterProgress}" } LaunchedEffect(Unit) { @@ -397,7 +421,7 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable { horizontalGestureSensitivity = horizontalGestureSensitivity, highlightedReading = mainState.value.highlightedReading, highlightedReadingThickness = highlightedReadingThickness, - progress = progress.value, + progress = progress, progressBar = mainState.value.progressBar, progressBarPadding = progressBarPadding, progressBarAlignment = mainState.value.progressBarAlignment, diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 55b5654a..903adf03 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -208,6 +208,7 @@ Поля Розташування Розмір шрифта + Підрахунок прогресу Режим відображення @@ -286,6 +287,10 @@ Звичайна Середня + + Відсотки + Кількість + Меркурій Нептун diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 02a9fc5d..66b9344d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -265,6 +265,7 @@ Margins Alignment Font size + Progress count Display mode @@ -343,6 +344,10 @@ Normal Medium + + Percentage + Quantity + Mercury Neptune