refactor: reader viewmodel

This commit is contained in:
Acclorite 2025-08-21 17:04:44 +03:00
parent 3817cc1980
commit 2ab62e629f
No known key found for this signature in database
GPG key ID: 6E54C611F6EE8593
17 changed files with 566 additions and 500 deletions

View file

@ -0,0 +1,45 @@
/*
* 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.domain.use_case.book
import ua.acclorite.book_story.core.helpers.coerceAndPreventNaN
import ua.acclorite.book_story.domain.model.reader.ReaderText
import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter
import javax.inject.Inject
class GetChapterProgressUseCase @Inject constructor() {
operator fun invoke(index: Int, text: List<ReaderText>): Pair<Chapter?, Float> {
fun getCurrentChapter(index: Int): Chapter? {
for (textIndex in index downTo 0) {
val readerText = text.getOrNull(textIndex) ?: break
if (readerText is Chapter) {
return readerText
}
}
return null
}
fun getCurrentChapterProgress(currentChapter: Chapter?): Float {
return currentChapter?.let { currentChapter ->
val startIndex = text.indexOf(currentChapter).coerceIn(0, text.lastIndex)
val endIndex = (text.indexOfFirst {
it is Chapter && text.indexOf(it) > startIndex
}.takeIf { it != -1 }) ?: (text.lastIndex + 1)
val currentIndexInChapter = (index - startIndex).coerceAtLeast(1)
val chapterLength = endIndex - (startIndex + 1)
(currentIndexInChapter / chapterLength.toFloat())
}.coerceAndPreventNaN()
}
val currentChapter = getCurrentChapter(index)
val currentChapterProgress = getCurrentChapterProgress(currentChapter)
return currentChapter to currentChapterProgress
}
}

View file

@ -400,6 +400,7 @@ class BookInfoModel @Inject constructor(
job.cancel()
job.join()
}
eventStack.clear()
_state.update { BookInfoState() }
}

View file

@ -0,0 +1,46 @@
/*
* 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.presentation.reader
import androidx.compose.runtime.Immutable
@Immutable
sealed class ReaderEffect {
data class OnSystemBarsVisibility(
val show: Boolean?
) : ReaderEffect()
data object OnResetBrightness : ReaderEffect()
data class OnScroll(
val scrollIndex: Int,
val scrollOffset: Int
) : ReaderEffect()
data class OnOpenTranslator(
val textToTranslate: String,
val translateWholeParagraph: Boolean
) : ReaderEffect()
data class OnOpenShareApp(
val textToShare: String
) : ReaderEffect()
data class OnOpenWebBrowser(
val textToSearch: String
) : ReaderEffect()
data class OnOpenDictionary(
val textToDefine: String
) : ReaderEffect()
data object OnNavigateBack : ReaderEffect()
data class OnNavigateToBookInfo(
val changePath: Boolean
) : ReaderEffect()
}

View file

@ -6,7 +6,6 @@
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
@ -14,16 +13,13 @@ import ua.acclorite.book_story.presentation.reader.model.Checkpoint
@Immutable
sealed class ReaderEvent {
data class OnLoadText(
val activity: ComponentActivity,
val fullscreenMode: Boolean
) : ReaderEvent()
data object OnLoadText : ReaderEvent()
data object OnRestoreScroll : ReaderEvent()
data class OnMenuVisibility(
val show: Boolean,
val fullscreenMode: Boolean,
val saveCheckpoint: Boolean,
val activity: ComponentActivity
val saveCheckpoint: Boolean
) : ReaderEvent()
data class OnChangeProgress(
@ -32,6 +28,10 @@ sealed class ReaderEvent {
val firstVisibleItemOffset: Int
) : ReaderEvent()
data class OnUpdateChapter(
val index: Int
) : ReaderEvent()
data class OnScrollToChapter(
val chapter: Chapter
) : ReaderEvent()
@ -45,29 +45,24 @@ sealed class ReaderEvent {
) : ReaderEvent()
data class OnLeave(
val activity: ComponentActivity,
val navigate: () -> Unit
) : ReaderEvent()
data class OnOpenTranslator(
val textToTranslate: String,
val translateWholeParagraph: Boolean,
val activity: ComponentActivity
val translateWholeParagraph: Boolean
) : ReaderEvent()
data class OnOpenShareApp(
val textToShare: String,
val activity: ComponentActivity
val textToShare: String
) : ReaderEvent()
data class OnOpenWebBrowser(
val textToSearch: String,
val activity: ComponentActivity
val textToSearch: String
) : ReaderEvent()
data class OnOpenDictionary(
val textToDefine: String,
val activity: ComponentActivity
val textToDefine: String
) : ReaderEvent()
data object OnShowSettingsBottomSheet : ReaderEvent()
@ -77,4 +72,10 @@ sealed class ReaderEvent {
data object OnShowChaptersDrawer : ReaderEvent()
data object OnDismissDrawer : ReaderEvent()
data object OnNavigateBack : ReaderEvent()
data class OnNavigateToBookInfo(
val changePath: Boolean
) : ReaderEvent()
}

View file

@ -6,59 +6,51 @@
package ua.acclorite.book_story.presentation.reader
import android.app.SearchManager
import android.content.Intent
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.snapshotFlow
import androidx.core.net.toUri
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import ua.acclorite.book_story.R
import ua.acclorite.book_story.core.helpers.coerceAndPreventNaN
import ua.acclorite.book_story.core.ui.UIText
import ua.acclorite.book_story.domain.model.reader.ReaderText.Chapter
import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase
import ua.acclorite.book_story.domain.use_case.book.GetChapterProgressUseCase
import ua.acclorite.book_story.domain.use_case.book.GetTextUseCase
import ua.acclorite.book_story.domain.use_case.book.UpdateBookUseCase
import ua.acclorite.book_story.domain.use_case.history.GetHistoryForBookUseCase
import ua.acclorite.book_story.presentation.history.HistoryScreen
import ua.acclorite.book_story.presentation.library.LibraryScreen
import ua.acclorite.book_story.presentation.reader.model.Checkpoint
import ua.acclorite.book_story.ui.common.helpers.launchActivity
import ua.acclorite.book_story.ui.common.helpers.setBrightness
import ua.acclorite.book_story.ui.common.helpers.showToast
import javax.inject.Inject
import kotlin.coroutines.coroutineContext
import kotlin.math.roundToInt
private const val READER = "READER, MODEL"
@HiltViewModel
class ReaderModel @Inject constructor(
private val updateBookUseCase: UpdateBookUseCase,
private val getTextUseCase: GetTextUseCase,
private val getBookUseCase: GetBookUseCase,
private val getHistoryForBookUseCase: GetHistoryForBookUseCase
private val getHistoryForBookUseCase: GetHistoryForBookUseCase,
private val getChapterProgressUseCase: GetChapterProgressUseCase
) : ViewModel() {
private val mutex = Mutex()
@ -66,38 +58,36 @@ class ReaderModel @Inject constructor(
private val _state = MutableStateFlow(ReaderState())
val state = _state.asStateFlow()
private var eventJob = SupervisorJob()
private var resetJob: Job? = null
private val _effects = MutableSharedFlow<ReaderEffect>()
val effects = _effects.asSharedFlow()
private var scrollJob: Job? = null
private val eventStack = mutableListOf<Job>()
fun onEvent(event: ReaderEvent) {
viewModelScope.launch(eventJob + Dispatchers.Main) {
viewModelScope.launch {
when (event) {
is ReaderEvent.OnLoadText -> {
launch(Dispatchers.IO) {
withContext(Dispatchers.Default) {
val text = getTextUseCase(_state.value.book.id)
yield()
ensureActive()
if (text.isEmpty()) {
_state.update {
it.copy(
isLoading = false,
errorMessage = UIText.StringResource(R.string.error_could_not_get_text)
errorMessage = UIText.StringResource(
resId = R.string.error_could_not_get_text
)
)
}
systemBarsVisibility(show = true, activity = event.activity)
return@launch
_effects.emit(ReaderEffect.OnSystemBarsVisibility(show = true))
return@withContext
}
systemBarsVisibility(
show = !event.fullscreenMode,
activity = event.activity
)
_effects.emit(ReaderEffect.OnSystemBarsVisibility(show = null))
val lastOpened = getHistoryForBookUseCase(_state.value.book.id)?.time
yield()
_state.update {
it.copy(
showMenu = false,
@ -107,54 +97,54 @@ class ReaderModel @Inject constructor(
text = text
)
}
yield()
ensureActive()
updateBookUseCase(_state.value.book)
LibraryScreen.refreshListChannel.trySend(0)
HistoryScreen.refreshListChannel.trySend(0)
launch {
snapshotFlow {
_state.value.listState.layoutInfo.totalItemsCount
}.collectLatest { itemsCount ->
if (itemsCount < _state.value.text.size) return@collectLatest
onEvent(ReaderEvent.OnRestoreScroll)
}
}
_state.value.book.apply {
_state.value.listState.requestScrollToItem(
scrollIndex,
scrollOffset
)
updateChapter(index = scrollIndex)
}
is ReaderEvent.OnRestoreScroll -> {
snapshotFlow { _state.value.listState.layoutInfo.totalItemsCount }.first { it > 0 }
_state.update {
it.copy(
isLoading = false,
errorMessage = null
)
}
_effects.emit(
ReaderEffect.OnScroll(
scrollIndex = _state.value.book.scrollIndex,
scrollOffset = _state.value.book.scrollOffset
)
)
return@collectLatest
}
}
_state.update {
val (currentChapter, currentChapterProgress) = getChapterProgressUseCase(
it.book.scrollIndex,
it.text
)
it.copy(
currentChapter = currentChapter,
currentChapterProgress = currentChapterProgress,
isLoading = false,
errorMessage = null
)
}
}
is ReaderEvent.OnMenuVisibility -> {
launch(Dispatchers.Default) {
if (_state.value.lockMenu) return@launch
withContext(Dispatchers.Default) {
if (_state.value.lockMenu) return@withContext
yield()
systemBarsVisibility(
show = event.show || !event.fullscreenMode,
activity = event.activity
_effects.emit(
ReaderEffect.OnSystemBarsVisibility(
show = if (event.show) true
else null
)
)
val checkpoints = _state.value.checkpoints.toMutableList()
if (event.saveCheckpoint && event.show) {
val checkpoints = _state.value.checkpoints.toMutableList()
checkpoints.removeIf {
it.index == _state.value.listState.firstVisibleItemIndex
}
@ -164,19 +154,20 @@ class ReaderModel @Inject constructor(
_state.value.listState.firstVisibleItemScrollOffset
)
)
_state.update {
it.copy(checkpoints = checkpoints)
}
}
_state.update {
it.copy(
showMenu = event.show,
checkpoints = checkpoints
)
it.copy(showMenu = event.show)
}
}
}
is ReaderEvent.OnChangeProgress -> {
launch(Dispatchers.Default) {
withContext(Dispatchers.Default) {
_state.update {
it.copy(
book = it.book.copy(
@ -194,44 +185,64 @@ class ReaderModel @Inject constructor(
}
}
is ReaderEvent.OnScrollToChapter -> {
launch(Dispatchers.Default) {
_state.value.apply {
val chapterIndex = text.indexOf(event.chapter).takeIf { it != -1 }
if (chapterIndex == null) {
return@launch
}
is ReaderEvent.OnUpdateChapter -> {
_state.update {
val (currentChapter, currentChapterProgress) = getChapterProgressUseCase(
event.index,
_state.value.text
)
it.copy(
currentChapter = currentChapter,
currentChapterProgress = currentChapterProgress
)
}
}
listState.requestScrollToItem(chapterIndex)
updateChapter(index = chapterIndex)
onEvent(
ReaderEvent.OnChangeProgress(
progress = calculateProgress(chapterIndex),
firstVisibleItemIndex = chapterIndex,
firstVisibleItemOffset = 0
)
is ReaderEvent.OnScrollToChapter -> {
withContext(Dispatchers.Default) {
val chapterIndex = _state.value.text
.indexOf(event.chapter)
.takeIf { it != -1 }
if (chapterIndex == null) return@withContext
_effects.emit(
ReaderEffect.OnScroll(
scrollIndex = chapterIndex,
scrollOffset = 0
)
}
)
onEvent(ReaderEvent.OnUpdateChapter(chapterIndex))
onEvent(
ReaderEvent.OnChangeProgress(
progress = calculateProgress(chapterIndex),
firstVisibleItemIndex = chapterIndex,
firstVisibleItemOffset = 0
)
)
}
}
is ReaderEvent.OnScroll -> {
scrollJob?.cancel()
scrollJob = launch(Dispatchers.Main) {
scrollJob = viewModelScope.launch(Dispatchers.IO) {
delay(300)
val scrollTo = (_state.value.text.lastIndex * event.progress).roundToInt()
updateChapter(scrollTo)
try {
_state.value.listState.requestScrollToItem(scrollTo)
} catch (_: Exception) {
}
_effects.emit(
ReaderEffect.OnScroll(
scrollIndex = scrollTo,
scrollOffset = 0
)
)
onEvent(ReaderEvent.OnUpdateChapter(scrollTo))
}
}
is ReaderEvent.OnRestoreCheckpoint -> {
launch(Dispatchers.Default) {
withContext(Dispatchers.Default) {
_state.update {
val checkpoints = it.checkpoints.toMutableList()
if (checkpoints.size > 1) checkpoints.remove(event.checkpoint)
@ -241,16 +252,14 @@ class ReaderModel @Inject constructor(
)
}
try {
_state.value.listState.requestScrollToItem(
event.checkpoint.index,
event.checkpoint.offset
_effects.emit(
ReaderEffect.OnScroll(
scrollIndex = event.checkpoint.index,
scrollOffset = event.checkpoint.offset
)
} catch (_: Exception) {
)
}
updateChapter(event.checkpoint.index)
onEvent(ReaderEvent.OnUpdateChapter(event.checkpoint.index))
onEvent(
ReaderEvent.OnChangeProgress(
progress = calculateProgress(event.checkpoint.index),
@ -262,186 +271,74 @@ class ReaderModel @Inject constructor(
}
is ReaderEvent.OnLeave -> {
launch(Dispatchers.Main) {
_state.update {
it.copy(
lockMenu = true
)
}
if (
!_state.value.isLoading &&
_state.value.listState.layoutInfo.totalItemsCount > 0 &&
_state.value.text.isNotEmpty() &&
_state.value.errorMessage != null
) {
_state.update {
it.copy(
lockMenu = true
book = it.book.copy(
progress = calculateProgress(),
scrollIndex = _state.value.listState.firstVisibleItemIndex,
scrollOffset = _state.value.listState.firstVisibleItemScrollOffset
)
)
}
_state.value.listState.apply {
if (
_state.value.isLoading ||
layoutInfo.totalItemsCount < 1 ||
_state.value.text.isEmpty() ||
_state.value.errorMessage != null
) return@apply
updateBookUseCase(_state.value.book)
_state.update {
it.copy(
book = it.book.copy(
progress = calculateProgress(),
scrollIndex = firstVisibleItemIndex,
scrollOffset = firstVisibleItemScrollOffset
)
)
}
updateBookUseCase(_state.value.book)
LibraryScreen.refreshListChannel.trySend(0)
HistoryScreen.refreshListChannel.trySend(0)
}
WindowCompat.getInsetsController(
event.activity.window,
event.activity.window.decorView
).show(WindowInsetsCompat.Type.systemBars())
event.activity.setBrightness(brightness = null)
event.navigate()
LibraryScreen.refreshListChannel.trySend(0)
HistoryScreen.refreshListChannel.trySend(0)
}
_effects.emit(
ReaderEffect.OnSystemBarsVisibility(
show = true
)
)
_effects.emit(ReaderEffect.OnResetBrightness)
event.navigate()
}
is ReaderEvent.OnOpenTranslator -> {
launch(Dispatchers.Default) {
val translatorIntent = Intent()
val browserIntent = Intent()
translatorIntent.type = "text/plain"
translatorIntent.action = Intent.ACTION_PROCESS_TEXT
browserIntent.action = Intent.ACTION_WEB_SEARCH
translatorIntent.putExtra(
Intent.EXTRA_PROCESS_TEXT,
event.textToTranslate
_effects.emit(
ReaderEffect.OnOpenTranslator(
textToTranslate = event.textToTranslate,
translateWholeParagraph = event.translateWholeParagraph
)
translatorIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true)
browserIntent.putExtra(
SearchManager.QUERY,
"translate: ${event.textToTranslate.trim()}"
)
yield()
translatorIntent.launchActivity(
activity = event.activity,
createChooser = !event.translateWholeParagraph,
success = {
return@launch
}
)
browserIntent.launchActivity(
activity = event.activity,
success = {
return@launch
}
)
withContext(Dispatchers.Main) {
event.activity.getString(R.string.error_no_translator)
.showToast(context = event.activity, longToast = false)
}
}
)
}
is ReaderEvent.OnOpenShareApp -> {
launch(Dispatchers.Default) {
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.type = "text/plain"
shareIntent.putExtra(
Intent.EXTRA_SUBJECT,
event.activity.getString(R.string.app_name)
_effects.emit(
ReaderEffect.OnOpenShareApp(
textToShare = event.textToShare
)
shareIntent.putExtra(
Intent.EXTRA_TEXT,
event.textToShare.trim()
)
yield()
shareIntent.launchActivity(
activity = event.activity,
createChooser = true,
success = {
return@launch
}
)
withContext(Dispatchers.Main) {
event.activity.getString(R.string.error_no_share_app)
.showToast(context = event.activity, longToast = false)
}
}
)
}
is ReaderEvent.OnOpenWebBrowser -> {
launch(Dispatchers.Default) {
val browserIntent = Intent()
browserIntent.action = Intent.ACTION_WEB_SEARCH
browserIntent.putExtra(
SearchManager.QUERY,
event.textToSearch
_effects.emit(
ReaderEffect.OnOpenWebBrowser(
textToSearch = event.textToSearch
)
yield()
browserIntent.launchActivity(
activity = event.activity,
success = {
return@launch
}
)
withContext(Dispatchers.Main) {
event.activity.getString(R.string.error_no_browser)
.showToast(context = event.activity, longToast = false)
}
}
)
}
is ReaderEvent.OnOpenDictionary -> {
launch(Dispatchers.Default) {
val dictionaryIntent = Intent()
val browserIntent = Intent()
dictionaryIntent.type = "text/plain"
dictionaryIntent.action = Intent.ACTION_PROCESS_TEXT
dictionaryIntent.putExtra(
Intent.EXTRA_PROCESS_TEXT,
event.textToDefine.trim()
_effects.emit(
ReaderEffect.OnOpenDictionary(
textToDefine = event.textToDefine
)
dictionaryIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true)
browserIntent.action = Intent.ACTION_VIEW
val text = event.textToDefine.trim().replace(" ", "+")
browserIntent.data = "https://www.onelook.com/?w=$text".toUri()
yield()
dictionaryIntent.launchActivity(
activity = event.activity,
createChooser = true,
success = {
return@launch
}
)
browserIntent.launchActivity(
activity = event.activity,
success = {
return@launch
}
)
withContext(Dispatchers.Main) {
event.activity.getString(R.string.error_no_dictionary)
.showToast(context = event.activity, longToast = false)
}
}
)
}
is ReaderEvent.OnShowSettingsBottomSheet -> {
@ -477,43 +374,57 @@ class ReaderModel @Inject constructor(
)
}
}
is ReaderEvent.OnNavigateBack -> {
_effects.emit(ReaderEffect.OnNavigateBack)
}
is ReaderEvent.OnNavigateToBookInfo -> {
_effects.emit(ReaderEffect.OnNavigateToBookInfo(event.changePath))
}
}
}
}.also { eventStack.add(it) }
}
fun init(
bookId: Int,
fullscreenMode: Boolean,
activity: ComponentActivity,
navigateBack: () -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
fun init(bookId: Int) {
viewModelScope.launch(Dispatchers.Default) {
val book = getBookUseCase(bookId)
if (book == null) {
navigateBack()
_effects.emit(ReaderEffect.OnNavigateBack)
return@launch
}
eventJob.cancel()
resetJob?.cancel()
eventJob.join()
resetJob?.join()
eventJob = SupervisorJob()
clear()
_state.update {
ReaderState(book = book)
ReaderState(
book = book
)
}
onEvent(
ReaderEvent.OnLoadText(
activity = activity,
fullscreenMode = fullscreenMode
)
)
onEvent(ReaderEvent.OnLoadText)
}
}
fun clearAsync() {
viewModelScope.launch {
eventStack.forEach { job ->
job.cancel()
}
_state.update { ReaderState() }
}
}
suspend fun clear() {
eventStack.forEach { job ->
job.cancel()
job.join()
}
eventStack.clear()
_state.update { ReaderState() }
}
@OptIn(FlowPreview::class)
suspend fun updateProgress(listState: LazyListState) {
snapshotFlow {
@ -527,12 +438,11 @@ class ReaderModel @Inject constructor(
) return@collectLatest
val progress = calculateProgress(index)
val (currentChapter, currentChapterProgress) = calculateCurrentChapter(index)
Log.i(
READER,
"Changed progress|currentChapter: $progress; ${currentChapter?.title}"
val (currentChapter, currentChapterProgress) = getChapterProgressUseCase(
index = index,
text = _state.value.text
)
_state.update {
it.copy(
book = it.book.copy(
@ -553,126 +463,42 @@ class ReaderModel @Inject constructor(
}
fun findChapterIndexAndLength(index: Int): Pair<Int, Int> {
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 (chapter, _) = getChapterProgressUseCase(index = index, text = _state.value.text)
return chapter?.let { chapter ->
val startIndex = _state.value.text
.indexOf(chapter)
.coerceIn(0, _state.value.text.lastIndex)
val endIndex = (_state.value.text.indexOfFirst {
it is Chapter && _state.value.text.indexOf(it) > startIndex
}.takeIf { it != -1 }) ?: (_state.value.text.lastIndex + 1)
val currentIndexInChapter = (index - startIndex).coerceAtLeast(1)
val chapterLength = endIndex - (startIndex + 1)
currentIndexInChapter to chapterLength
}
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)
_state.update {
Log.i(
READER,
"Changed currentChapter|currentChapterProgress:" +
" ${currentChapter?.title}($currentChapterProgress)"
)
it.copy(
currentChapter = currentChapter,
currentChapterProgress = currentChapterProgress
)
}
}
}
private fun calculateCurrentChapter(index: Int): Pair<Chapter?, Float> {
val currentChapter = findCurrentChapter(index)
val currentChapterProgress = currentChapter?.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 / chapterLength.toFloat())
}
}.coerceAndPreventNaN()
return currentChapter to currentChapterProgress
}
private fun findCurrentChapter(index: Int): Chapter? {
return try {
for (textIndex in index downTo 0) {
val readerText = _state.value.text.getOrNull(textIndex) ?: break
if (readerText is Chapter) {
return readerText
}
}
null
} catch (e: Exception) {
e.printStackTrace()
null
}
}
private fun calculateProgress(firstVisibleItemIndex: Int? = null): Float {
return _state.value.run {
if (
isLoading ||
listState.layoutInfo.totalItemsCount == 0 ||
text.isEmpty() ||
errorMessage != null
) {
return book.progress
}
if (
_state.value.isLoading ||
_state.value.listState.layoutInfo.totalItemsCount == 0 ||
_state.value.text.isEmpty() ||
_state.value.errorMessage != null
) return _state.value.book.progress
if ((firstVisibleItemIndex ?: listState.firstVisibleItemIndex) == 0) {
return 0f
}
if ((firstVisibleItemIndex ?: _state.value.listState.firstVisibleItemIndex) == 0) return 0f
val lastVisibleItemIndex = listState.layoutInfo.visibleItemsInfo.last().index
if (lastVisibleItemIndex >= text.lastIndex) {
return 1f
}
val lastVisibleItemIndex = _state.value.listState.layoutInfo.visibleItemsInfo.last().index
if (lastVisibleItemIndex >= _state.value.text.lastIndex) return 1f
return@run (firstVisibleItemIndex ?: listState.firstVisibleItemIndex)
.div(text.lastIndex.toFloat())
.coerceAndPreventNaN()
}
}
private suspend fun systemBarsVisibility(
show: Boolean,
activity: ComponentActivity
) {
withContext(Dispatchers.Main) {
WindowCompat.getInsetsController(
activity.window,
activity.window.decorView
).apply {
systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
if (show) show(WindowInsetsCompat.Type.systemBars())
else hide(WindowInsetsCompat.Type.systemBars())
}
}
}
fun resetScreen() {
resetJob = viewModelScope.launch(Dispatchers.Main) {
eventJob.cancel()
eventJob = SupervisorJob()
yield()
_state.update { ReaderState() }
}
return (firstVisibleItemIndex ?: _state.value.listState.firstVisibleItemIndex)
.div(_state.value.text.lastIndex.toFloat())
.coerceAndPreventNaN()
}
private suspend inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
mutex.withLock {
yield()
coroutineContext.ensureActive()
this.value = function(this.value)
}
}

View file

@ -45,7 +45,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.parcelize.Parcelize
import ua.acclorite.book_story.core.helpers.calculateProgress
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
import ua.acclorite.book_story.presentation.navigator.Screen
import ua.acclorite.book_story.presentation.reader.model.ReaderColorEffects
import ua.acclorite.book_story.presentation.reader.model.ReaderProgressCount
@ -54,8 +53,8 @@ import ua.acclorite.book_story.presentation.settings.SettingsModel
import ua.acclorite.book_story.ui.common.helpers.LocalActivity
import ua.acclorite.book_story.ui.common.helpers.LocalSettings
import ua.acclorite.book_story.ui.common.helpers.setBrightness
import ua.acclorite.book_story.ui.navigator.LocalNavigator
import ua.acclorite.book_story.ui.reader.ReaderContent
import ua.acclorite.book_story.ui.reader.ReaderEffects
import kotlin.math.roundToInt
@Parcelize
@ -69,7 +68,6 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
@OptIn(ExperimentalLayoutApi::class)
@Composable
override fun Content() {
val navigator = LocalNavigator.current
val screenModel = hiltViewModel<ReaderModel>()
val settingsModel = hiltViewModel<SettingsModel>()
val settings = LocalSettings.current
@ -102,9 +100,7 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
screenModel.onEvent(
ReaderEvent.OnMenuVisibility(
show = false,
fullscreenMode = settings.fullscreen.lastValue,
saveCheckpoint = false,
activity = activity
saveCheckpoint = false
)
)
}
@ -328,22 +324,13 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
}
LaunchedEffect(Unit) {
screenModel.init(
bookId = bookId,
fullscreenMode = settings.fullscreen.lastValue,
activity = activity,
navigateBack = {
navigator.pop()
}
)
screenModel.init(bookId = bookId)
}
LaunchedEffect(settings.fullscreen.value) {
screenModel.onEvent(
ReaderEvent.OnMenuVisibility(
show = state.value.showMenu,
fullscreenMode = settings.fullscreen.lastValue,
saveCheckpoint = false,
activity = activity
saveCheckpoint = false
)
)
}
@ -382,7 +369,7 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
DisposableEffect(Unit) {
onDispose {
screenModel.resetScreen()
screenModel.clearAsync()
WindowCompat.getInsetsController(
activity.window,
activity.window.decorView
@ -390,6 +377,13 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
}
}
ReaderEffects(
effects = screenModel.effects,
book = state.value.book,
listState = listState,
fullscreen = settings.fullscreen.value
)
ReaderContent(
book = state.value.book,
text = state.value.text,
@ -444,7 +438,6 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
letterSpacing = letterSpacing,
paragraphIndentation = paragraphIndentation,
doubleClickTranslation = settings.doubleClickTranslation.value,
fullscreenMode = settings.fullscreen.value,
selectPreviousPreset = settingsModel::onEvent,
selectNextPreset = settingsModel::onEvent,
leave = screenModel::onEvent,
@ -461,19 +454,8 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable {
dismissBottomSheet = screenModel::onEvent,
showChaptersDrawer = screenModel::onEvent,
dismissDrawer = screenModel::onEvent,
navigateBack = {
navigator.pop()
},
navigateToBookInfo = { changePath ->
if (changePath) BookInfoScreen.changePathChannel.trySend(true)
navigator.push(
BookInfoScreen(
bookId = bookId,
),
popping = true,
saveInBackStack = false
)
}
navigateBack = screenModel::onEvent,
navigateToBookInfo = screenModel::onEvent
)
}
}

View file

@ -9,21 +9,17 @@ package ua.acclorite.book_story.ui.reader
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import ua.acclorite.book_story.presentation.reader.ReaderEvent
import ua.acclorite.book_story.ui.common.helpers.LocalActivity
@Composable
fun ReaderBackHandler(
leave: (ReaderEvent.OnLeave) -> Unit,
navigateBack: () -> Unit
navigateBack: (ReaderEvent.OnNavigateBack) -> Unit
) {
val activity = LocalActivity.current
BackHandler {
leave(
ReaderEvent.OnLeave(
activity = activity,
navigate = {
navigateBack()
navigateBack(ReaderEvent.OnNavigateBack)
}
)
)

View file

@ -14,14 +14,12 @@ import ua.acclorite.book_story.presentation.reader.ReaderScreen
@Composable
fun ReaderBottomSheet(
bottomSheet: BottomSheet?,
fullscreenMode: Boolean,
menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit,
dismissBottomSheet: (ReaderEvent.OnDismissBottomSheet) -> Unit
) {
when (bottomSheet) {
ReaderScreen.SETTINGS_BOTTOM_SHEET -> {
ReaderSettingsBottomSheet(
fullscreenMode = fullscreenMode,
menuVisibility = menuVisibility,
dismissBottomSheet = dismissBottomSheet
)

View file

@ -88,7 +88,6 @@ fun ReaderContent(
letterSpacing: TextUnit,
paragraphIndentation: TextUnit,
doubleClickTranslation: Boolean,
fullscreenMode: Boolean,
selectPreviousPreset: (SettingsEvent.OnSelectPreviousPreset) -> Unit,
selectNextPreset: (SettingsEvent.OnSelectNextPreset) -> Unit,
menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit,
@ -105,12 +104,11 @@ fun ReaderContent(
dismissBottomSheet: (ReaderEvent.OnDismissBottomSheet) -> Unit,
showChaptersDrawer: (ReaderEvent.OnShowChaptersDrawer) -> Unit,
dismissDrawer: (ReaderEvent.OnDismissDrawer) -> Unit,
navigateToBookInfo: (changePath: Boolean) -> Unit,
navigateBack: () -> Unit
navigateToBookInfo: (ReaderEvent.OnNavigateToBookInfo) -> Unit,
navigateBack: (ReaderEvent.OnNavigateBack) -> Unit
) {
ReaderBottomSheet(
bottomSheet = bottomSheet,
fullscreenMode = fullscreenMode,
menuVisibility = menuVisibility,
dismissBottomSheet = dismissBottomSheet
)
@ -167,7 +165,6 @@ fun ReaderContent(
letterSpacing = letterSpacing,
paragraphIndentation = paragraphIndentation,
doubleClickTranslation = doubleClickTranslation,
fullscreenMode = fullscreenMode,
selectPreviousPreset = selectPreviousPreset,
selectNextPreset = selectNextPreset,
menuVisibility = menuVisibility,

View file

@ -0,0 +1,198 @@
/*
* 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 android.app.SearchManager
import android.content.Intent
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.core.net.toUri
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import kotlinx.coroutines.flow.SharedFlow
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.library.Book
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
import ua.acclorite.book_story.presentation.reader.ReaderEffect
import ua.acclorite.book_story.ui.common.helpers.LocalActivity
import ua.acclorite.book_story.ui.common.helpers.launchActivity
import ua.acclorite.book_story.ui.common.helpers.setBrightness
import ua.acclorite.book_story.ui.common.helpers.showToast
import ua.acclorite.book_story.ui.navigator.LocalNavigator
@Composable
fun ReaderEffects(
effects: SharedFlow<ReaderEffect>,
book: Book,
listState: LazyListState,
fullscreen: Boolean
) {
val navigator = LocalNavigator.current
val activity = LocalActivity.current
LaunchedEffect(effects, book, listState, fullscreen) {
effects.collect { effect ->
when (effect) {
is ReaderEffect.OnSystemBarsVisibility -> {
WindowCompat.getInsetsController(
activity.window,
activity.window.decorView
).apply {
systemBarsBehavior = WindowInsetsControllerCompat
.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
if (effect.show ?: !fullscreen) show(WindowInsetsCompat.Type.systemBars())
else hide(WindowInsetsCompat.Type.systemBars())
}
}
is ReaderEffect.OnResetBrightness -> {
activity.setBrightness(brightness = null)
}
is ReaderEffect.OnScroll -> {
listState.requestScrollToItem(
effect.scrollIndex,
effect.scrollOffset
)
}
is ReaderEffect.OnOpenTranslator -> {
val translatorIntent = Intent()
val browserIntent = Intent()
translatorIntent.type = "text/plain"
translatorIntent.action = Intent.ACTION_PROCESS_TEXT
browserIntent.action = Intent.ACTION_WEB_SEARCH
translatorIntent.putExtra(
Intent.EXTRA_PROCESS_TEXT,
effect.textToTranslate
)
translatorIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true)
browserIntent.putExtra(
SearchManager.QUERY,
"translate: ${effect.textToTranslate.trim()}"
)
translatorIntent.launchActivity(
activity = activity,
createChooser = !effect.translateWholeParagraph,
success = {
return@collect
}
)
browserIntent.launchActivity(
activity = activity,
success = {
return@collect
}
)
activity.getString(R.string.error_no_translator)
.showToast(context = activity, longToast = false)
}
is ReaderEffect.OnOpenShareApp -> {
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.type = "text/plain"
shareIntent.putExtra(
Intent.EXTRA_SUBJECT,
activity.getString(R.string.app_name)
)
shareIntent.putExtra(
Intent.EXTRA_TEXT,
effect.textToShare.trim()
)
shareIntent.launchActivity(
activity = activity,
createChooser = true,
success = {
return@collect
}
)
activity.getString(R.string.error_no_share_app)
.showToast(context = activity, longToast = false)
}
is ReaderEffect.OnOpenWebBrowser -> {
val browserIntent = Intent()
browserIntent.action = Intent.ACTION_WEB_SEARCH
browserIntent.putExtra(
SearchManager.QUERY,
effect.textToSearch
)
browserIntent.launchActivity(
activity = activity,
success = {
return@collect
}
)
activity.getString(R.string.error_no_browser)
.showToast(context = activity, longToast = false)
}
is ReaderEffect.OnOpenDictionary -> {
val dictionaryIntent = Intent()
val browserIntent = Intent()
dictionaryIntent.type = "text/plain"
dictionaryIntent.action = Intent.ACTION_PROCESS_TEXT
dictionaryIntent.putExtra(
Intent.EXTRA_PROCESS_TEXT,
effect.textToDefine.trim()
)
dictionaryIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true)
browserIntent.action = Intent.ACTION_VIEW
val text = effect.textToDefine.trim().replace(" ", "+")
browserIntent.data = "https://www.onelook.com/?w=$text".toUri()
dictionaryIntent.launchActivity(
activity = activity,
createChooser = true,
success = {
return@collect
}
)
browserIntent.launchActivity(
activity = activity,
success = {
return@collect
}
)
activity.getString(R.string.error_no_dictionary)
.showToast(context = activity, longToast = false)
}
is ReaderEffect.OnNavigateBack -> {
navigator.pop()
}
is ReaderEffect.OnNavigateToBookInfo -> {
if (effect.changePath) BookInfoScreen.changePathChannel.trySend(true)
navigator.push(
BookInfoScreen(
bookId = book.id
),
popping = true,
saveInBackStack = false
)
}
}
}
}
}

View file

@ -24,7 +24,6 @@ import ua.acclorite.book_story.presentation.reader.ReaderEvent
import ua.acclorite.book_story.ui.common.components.placeholder.ErrorPlaceholder
import ua.acclorite.book_story.ui.common.components.top_bar.TopAppBar
import ua.acclorite.book_story.ui.common.components.top_bar.TopAppBarData
import ua.acclorite.book_story.ui.common.helpers.LocalActivity
import ua.acclorite.book_story.ui.navigator.NavigatorBackIconButton
@OptIn(ExperimentalMaterial3Api::class)
@ -33,10 +32,9 @@ import ua.acclorite.book_story.ui.navigator.NavigatorBackIconButton
fun ReaderErrorPlaceholder(
errorMessage: UIText,
leave: (ReaderEvent.OnLeave) -> Unit,
navigateToBookInfo: (changePath: Boolean) -> Unit,
navigateBack: () -> Unit
navigateToBookInfo: (ReaderEvent.OnNavigateToBookInfo) -> Unit,
navigateBack: (ReaderEvent.OnNavigateBack) -> Unit
) {
val activity = LocalActivity.current
Scaffold(
modifier = Modifier.fillMaxSize(),
containerColor = MaterialTheme.colorScheme.surface,
@ -52,7 +50,7 @@ fun ReaderErrorPlaceholder(
contentID = 0,
contentNavigationIcon = {
NavigatorBackIconButton {
navigateBack()
navigateBack(ReaderEvent.OnNavigateBack)
}
},
contentTitle = {},
@ -73,9 +71,12 @@ fun ReaderErrorPlaceholder(
action = {
leave(
ReaderEvent.OnLeave(
activity = activity,
navigate = {
navigateToBookInfo(true)
navigateToBookInfo(
ReaderEvent.OnNavigateToBookInfo(
changePath = true
)
)
}
)
)

View file

@ -87,7 +87,6 @@ fun ReaderLayout(
letterSpacing: TextUnit,
paragraphIndentation: TextUnit,
doubleClickTranslation: Boolean,
fullscreenMode: Boolean,
isLoading: Boolean,
showMenu: Boolean,
menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit,
@ -107,16 +106,14 @@ fun ReaderLayout(
onShareRequested = { textToShare ->
openShareApp(
ReaderEvent.OnOpenShareApp(
textToShare = textToShare,
activity = activity
textToShare = textToShare
)
)
},
onWebSearchRequested = { textToSearch ->
openWebBrowser(
ReaderEvent.OnOpenWebBrowser(
textToSearch = textToSearch,
activity = activity
textToSearch = textToSearch
)
)
},
@ -124,16 +121,14 @@ fun ReaderLayout(
openTranslator(
ReaderEvent.OnOpenTranslator(
textToTranslate = textToTranslate,
translateWholeParagraph = false,
activity = activity
translateWholeParagraph = false
)
)
},
onDictionaryRequested = { textToDefine ->
openDictionary(
ReaderEvent.OnOpenDictionary(
textToDefine,
activity = activity
textToDefine = textToDefine
)
)
}
@ -149,9 +144,7 @@ fun ReaderLayout(
menuVisibility(
ReaderEvent.OnMenuVisibility(
show = !showMenu,
fullscreenMode = fullscreenMode,
saveCheckpoint = true,
activity = activity
saveCheckpoint = true
)
)
}
@ -199,7 +192,6 @@ fun ReaderLayout(
spacing = paragraphHeight
) {
ReaderLayoutText(
activity = activity,
showMenu = showMenu,
entry = entry,
imagesCornersRoundness = imagesCornersRoundness,
@ -218,7 +210,6 @@ fun ReaderLayout(
letterSpacing = letterSpacing,
sidePadding = sidePadding,
paragraphIndentation = paragraphIndentation,
fullscreenMode = fullscreenMode,
doubleClickTranslation = doubleClickTranslation,
highlightedReading = highlightedReading,
highlightedReadingThickness = highlightedReadingThickness,

View file

@ -6,7 +6,6 @@
package ua.acclorite.book_story.ui.reader
import androidx.activity.ComponentActivity
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -25,7 +24,6 @@ import ua.acclorite.book_story.ui.theme.model.HorizontalAlignment
@Composable
fun LazyItemScope.ReaderLayoutText(
activity: ComponentActivity,
showMenu: Boolean,
entry: ReaderText,
imagesCornersRoundness: Dp,
@ -44,7 +42,6 @@ fun LazyItemScope.ReaderLayoutText(
letterSpacing: TextUnit,
sidePadding: Dp,
paragraphIndentation: TextUnit,
fullscreenMode: Boolean,
doubleClickTranslation: Boolean,
highlightedReading: Boolean,
highlightedReadingThickness: FontWeight,
@ -85,7 +82,6 @@ fun LazyItemScope.ReaderLayoutText(
is ReaderText.Text -> {
ReaderLayoutTextParagraph(
paragraph = entry,
activity = activity,
showMenu = showMenu,
fontFamily = fontFamily,
fontColor = fontColor,
@ -98,7 +94,6 @@ fun LazyItemScope.ReaderLayoutText(
letterSpacing = letterSpacing,
sidePadding = sidePadding,
paragraphIndentation = paragraphIndentation,
fullscreenMode = fullscreenMode,
doubleClickTranslation = doubleClickTranslation,
highlightedReading = highlightedReading,
highlightedReadingThickness = highlightedReadingThickness,

View file

@ -6,7 +6,6 @@
package ua.acclorite.book_story.ui.reader
import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
@ -34,7 +33,6 @@ import ua.acclorite.book_story.ui.reader.model.FontWithName
@Composable
fun LazyItemScope.ReaderLayoutTextParagraph(
paragraph: Text,
activity: ComponentActivity,
showMenu: Boolean,
fontFamily: FontWithName,
fontColor: Color,
@ -47,7 +45,6 @@ fun LazyItemScope.ReaderLayoutTextParagraph(
letterSpacing: TextUnit,
sidePadding: Dp,
paragraphIndentation: TextUnit,
fullscreenMode: Boolean,
doubleClickTranslation: Boolean,
highlightedReading: Boolean,
highlightedReadingThickness: FontWeight,
@ -72,8 +69,7 @@ fun LazyItemScope.ReaderLayoutTextParagraph(
openTranslator(
ReaderEvent.OnOpenTranslator(
textToTranslate = paragraph.line.text,
translateWholeParagraph = true,
activity = activity
translateWholeParagraph = true
)
)
},
@ -81,9 +77,7 @@ fun LazyItemScope.ReaderLayoutTextParagraph(
menuVisibility(
ReaderEvent.OnMenuVisibility(
show = !showMenu,
fullscreenMode = fullscreenMode,
saveCheckpoint = true,
activity = activity
saveCheckpoint = true
)
)
}

View file

@ -92,7 +92,6 @@ fun ReaderScaffold(
letterSpacing: TextUnit,
paragraphIndentation: TextUnit,
doubleClickTranslation: Boolean,
fullscreenMode: Boolean,
selectPreviousPreset: (SettingsEvent.OnSelectPreviousPreset) -> Unit,
selectNextPreset: (SettingsEvent.OnSelectNextPreset) -> Unit,
menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit,
@ -106,8 +105,8 @@ fun ReaderScaffold(
openDictionary: (ReaderEvent.OnOpenDictionary) -> Unit,
showSettingsBottomSheet: (ReaderEvent.OnShowSettingsBottomSheet) -> Unit,
showChaptersDrawer: (ReaderEvent.OnShowChaptersDrawer) -> Unit,
navigateToBookInfo: (changePath: Boolean) -> Unit,
navigateBack: () -> Unit
navigateToBookInfo: (ReaderEvent.OnNavigateToBookInfo) -> Unit,
navigateBack: (ReaderEvent.OnNavigateBack) -> Unit
) {
Scaffold(
Modifier
@ -197,7 +196,6 @@ fun ReaderScaffold(
letterSpacing = letterSpacing,
paragraphIndentation = paragraphIndentation,
doubleClickTranslation = doubleClickTranslation,
fullscreenMode = fullscreenMode,
isLoading = isLoading,
showMenu = showMenu,
menuVisibility = menuVisibility,

View file

@ -28,7 +28,6 @@ import kotlinx.coroutines.launch
import ua.acclorite.book_story.presentation.reader.ReaderEvent
import ua.acclorite.book_story.ui.common.components.common.LazyColumnWithScrollbar
import ua.acclorite.book_story.ui.common.components.modal_bottom_sheet.ModalBottomSheet
import ua.acclorite.book_story.ui.common.helpers.LocalActivity
import ua.acclorite.book_story.ui.settings.appearance.colors.ColorsSubcategory
import ua.acclorite.book_story.ui.settings.reader.chapters.ChaptersSubcategory
import ua.acclorite.book_story.ui.settings.reader.font.FontSubcategory
@ -47,11 +46,9 @@ private var initialPage = 0
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReaderSettingsBottomSheet(
fullscreenMode: Boolean,
menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit,
dismissBottomSheet: (ReaderEvent.OnDismissBottomSheet) -> Unit
) {
val activity = LocalActivity.current
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(initialPage) { 3 }
DisposableEffect(Unit) { onDispose { initialPage = pagerState.currentPage } }
@ -70,9 +67,7 @@ fun ReaderSettingsBottomSheet(
menuVisibility(
ReaderEvent.OnMenuVisibility(
show = pagerState.currentPage != 2,
fullscreenMode = fullscreenMode,
saveCheckpoint = false,
activity = activity
saveCheckpoint = false
)
)
}

View file

@ -53,8 +53,8 @@ fun ReaderTopBar(
selectNextPreset: (SettingsEvent.OnSelectNextPreset) -> Unit,
showSettingsBottomSheet: (ReaderEvent.OnShowSettingsBottomSheet) -> Unit,
showChaptersDrawer: (ReaderEvent.OnShowChaptersDrawer) -> Unit,
navigateToBookInfo: (changePath: Boolean) -> Unit,
navigateBack: () -> Unit
navigateToBookInfo: (ReaderEvent.OnNavigateToBookInfo) -> Unit,
navigateBack: (ReaderEvent.OnNavigateBack) -> Unit
) {
val activity = LocalActivity.current
val animatedChapterProgress = animateFloatAsState(
@ -81,9 +81,8 @@ fun ReaderTopBar(
) {
leave(
ReaderEvent.OnLeave(
activity = activity,
navigate = {
navigateBack()
navigateBack(ReaderEvent.OnNavigateBack)
}
)
)
@ -99,9 +98,12 @@ fun ReaderTopBar(
onClick = {
leave(
ReaderEvent.OnLeave(
activity = activity,
navigate = {
navigateToBookInfo(false)
navigateToBookInfo(
ReaderEvent.OnNavigateToBookInfo(
changePath = false
)
)
}
)
)