🚀 Improve progress display in Reader

* Book and chapter (in "($cp)") progress
* Sync chapter & book progress calculation

Resolves: #103
This commit is contained in:
Acclorite 2024-10-11 20:06:11 +03:00
parent 2e2dee9e26
commit 7690cafec5
3 changed files with 50 additions and 52 deletions

View file

@ -168,7 +168,6 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
onLibraryEvent(LibraryEvent.OnUpdateBook(it)) onLibraryEvent(LibraryEvent.OnUpdateBook(it))
onHistoryEvent(HistoryEvent.OnUpdateBook(it)) onHistoryEvent(HistoryEvent.OnUpdateBook(it))
} }
viewModel.onUpdateCurrentChapter()
} }
DisposableEffect(mainState.value.screenOrientation) { DisposableEffect(mainState.value.screenOrientation) {
context.requestedOrientation = mainState.value.screenOrientation.code context.requestedOrientation = mainState.value.screenOrientation.code

View file

@ -53,11 +53,23 @@ fun ReaderBottomBar() {
val onLibraryEvent = LocalLibraryViewModel.current.onEvent val onLibraryEvent = LocalLibraryViewModel.current.onEvent
val onHistoryEvent = LocalHistoryViewModel.current.onEvent val onHistoryEvent = LocalHistoryViewModel.current.onEvent
val bookProgress by remember {
derivedStateOf {
"${state.value.book.progress.calculateProgress(2)}%"
}
}
val chapterProgress by remember {
derivedStateOf {
if (state.value.currentChapter == null) return@derivedStateOf ""
" (${state.value.currentChapterProgress.calculateProgress(2)}%)"
}
}
val progress by remember { val progress by remember {
derivedStateOf { derivedStateOf {
"${state.value.book.progress.calculateProgress(4)}%" "$bookProgress$chapterProgress"
} }
} }
val arrowDirection by remember { val arrowDirection by remember {
derivedStateOf { derivedStateOf {
val checkpoint = state.value.checkpoint.first val checkpoint = state.value.checkpoint.first

View file

@ -26,6 +26,7 @@ import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Chapter
import ua.acclorite.book_story.domain.use_case.book.CheckForTextUpdate import ua.acclorite.book_story.domain.use_case.book.CheckForTextUpdate
import ua.acclorite.book_story.domain.use_case.book.GetBookById import ua.acclorite.book_story.domain.use_case.book.GetBookById
import ua.acclorite.book_story.domain.use_case.book.GetText import ua.acclorite.book_story.domain.use_case.book.GetText
@ -96,10 +97,13 @@ class ReaderViewModel @Inject constructor(
}.collectLatest { itemsCount -> }.collectLatest { itemsCount ->
if (itemsCount < _state.value.text.size) return@collectLatest if (itemsCount < _state.value.text.size) return@collectLatest
_state.value.book.apply {
_state.value.listState.requestScrollToItem( _state.value.listState.requestScrollToItem(
_state.value.book.scrollIndex, scrollIndex,
_state.value.book.scrollOffset scrollOffset
) )
updateChapter(index = scrollIndex)
}
event.checkForUpdate() event.checkForUpdate()
@ -564,53 +568,41 @@ class ReaderViewModel @Inject constructor(
fun onUpdateProgress(refreshList: (Book) -> Unit) { fun onUpdateProgress(refreshList: (Book) -> Unit) {
viewModelScope.launch(Dispatchers.Main) { viewModelScope.launch(Dispatchers.Main) {
snapshotFlow { snapshotFlow {
_state.value.listState.firstVisibleItemIndex to _state.value.listState.firstVisibleItemScrollOffset _state.value.listState.run { firstVisibleItemIndex to firstVisibleItemScrollOffset }
} }.distinctUntilChanged().debounce(300).collectLatest { (index, offset) ->
.distinctUntilChanged() val progress = calculateProgress(index)
.debounce(300) if (progress == _state.value.book.progress) return@collectLatest
.collectLatest { (firstVisibleItemIndex, firstVisibleItemOffset) -> val (currentChapter, currentChapterProgress) = calculateCurrentChapter(index)
calculateProgress(firstVisibleItemIndex).apply {
if (this == _state.value.book.progress) return@apply
onEvent(
ReaderEvent.OnChangeProgress(
progress = this,
firstVisibleItemIndex = firstVisibleItemIndex,
firstVisibleItemOffset = firstVisibleItemOffset,
refreshList = { book ->
refreshList(book)
}
)
)
}
}
}
}
@OptIn(FlowPreview::class)
fun onUpdateCurrentChapter() {
viewModelScope.launch(Dispatchers.Main) {
snapshotFlow {
_state.value.listState.firstVisibleItemIndex
}
.distinctUntilChanged()
.debounce(300)
.collectLatest { index ->
if (_state.value.book.chapters.isEmpty()) {
_state.update { _state.update {
it.copy( it.copy(
currentChapter = null book = it.book.copy(
progress = progress,
scrollIndex = index,
scrollOffset = offset
),
currentChapter = currentChapter,
currentChapterProgress = currentChapterProgress
) )
} }
return@collectLatest
}
updateChapter(index) updateBook.execute(_state.value.book)
refreshList(_state.value.book)
} }
} }
} }
private fun updateChapter(index: Int) { private fun updateChapter(index: Int) {
val (currentChapter, currentChapterProgress) = calculateCurrentChapter(index)
_state.update {
it.copy(
currentChapter = currentChapter,
currentChapterProgress = currentChapterProgress
)
}
}
private fun calculateCurrentChapter(index: Int): Pair<Chapter?, Float> {
val currentChapter = _state.value.book.chapters.find { chapter -> val currentChapter = _state.value.book.chapters.find { chapter ->
index in chapter.startIndex..chapter.endIndex index in chapter.startIndex..chapter.endIndex
} }
@ -622,12 +614,7 @@ class ReaderViewModel @Inject constructor(
(currentIndex / endIndex.toFloat()) (currentIndex / endIndex.toFloat())
}.coerceAndPreventNaN() }.coerceAndPreventNaN()
_state.update { return currentChapter to currentChapterProgress
it.copy(
currentChapter = currentChapter,
currentChapterProgress = currentChapterProgress
)
}
} }
private fun calculateProgress(firstVisibleItemIndex: Int? = null): Float { private fun calculateProgress(firstVisibleItemIndex: Int? = null): Float {