From 2ab62e629f366bb71d8973c91664e9be3b77d6e1 Mon Sep 17 00:00:00 2001 From: Acclorite <2ma0bhpk@anonaddy.com> Date: Thu, 21 Aug 2025 17:04:44 +0300 Subject: [PATCH] refactor: reader viewmodel --- .../book/GetChapterProgressUseCase.kt | 45 ++ .../presentation/book_info/BookInfoModel.kt | 1 + .../presentation/reader/ReaderEffect.kt | 46 ++ .../presentation/reader/ReaderEvent.kt | 35 +- .../presentation/reader/ReaderModel.kt | 602 +++++++----------- .../presentation/reader/ReaderScreen.kt | 46 +- .../book_story/ui/reader/ReaderBackHandler.kt | 8 +- .../book_story/ui/reader/ReaderBottomSheet.kt | 2 - .../book_story/ui/reader/ReaderContent.kt | 7 +- .../book_story/ui/reader/ReaderEffects.kt | 198 ++++++ .../ui/reader/ReaderErrorPlaceholder.kt | 15 +- .../book_story/ui/reader/ReaderLayout.kt | 19 +- .../book_story/ui/reader/ReaderLayoutText.kt | 5 - .../ui/reader/ReaderLayoutTextParagraph.kt | 10 +- .../book_story/ui/reader/ReaderScaffold.kt | 6 +- .../ui/reader/ReaderSettingsBottomSheet.kt | 7 +- .../book_story/ui/reader/ReaderTopBar.kt | 14 +- 17 files changed, 566 insertions(+), 500 deletions(-) create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetChapterProgressUseCase.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderEffect.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderEffects.kt diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetChapterProgressUseCase.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetChapterProgressUseCase.kt new file mode 100644 index 00000000..cfb0185c --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetChapterProgressUseCase.kt @@ -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): Pair { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoModel.kt index afd79cba..f0dd87e7 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/book_info/BookInfoModel.kt @@ -400,6 +400,7 @@ class BookInfoModel @Inject constructor( job.cancel() job.join() } + eventStack.clear() _state.update { BookInfoState() } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderEffect.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderEffect.kt new file mode 100644 index 00000000..9e31e2d5 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderEffect.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderEvent.kt index 3e52dfd8..9bc0bf55 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderEvent.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderModel.kt index a4fe9fd2..af35ec47 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderModel.kt @@ -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() + val effects = _effects.asSharedFlow() private var scrollJob: Job? = null + private val eventStack = mutableListOf() 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 { - 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 { - 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 MutableStateFlow.update(function: (T) -> T) { mutex.withLock { - yield() + coroutineContext.ensureActive() this.value = function(this.value) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderScreen.kt index 9c0575cc..653f31bf 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderScreen.kt @@ -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() val settingsModel = hiltViewModel() 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 ) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderBackHandler.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderBackHandler.kt index 7d78385d..1ac697e2 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderBackHandler.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderBackHandler.kt @@ -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) } ) ) diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderBottomSheet.kt index 3f472eae..382ddc07 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderBottomSheet.kt @@ -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 ) diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderContent.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderContent.kt index 002ecf78..6229e72e 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderContent.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderContent.kt @@ -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, diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderEffects.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderEffects.kt new file mode 100644 index 00000000..f986fc97 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderEffects.kt @@ -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, + 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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderErrorPlaceholder.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderErrorPlaceholder.kt index 35d5025d..39b1bb27 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderErrorPlaceholder.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderErrorPlaceholder.kt @@ -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 + ) + ) } ) ) diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayout.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayout.kt index f254328b..4cf903d4 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayout.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayout.kt @@ -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, diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayoutText.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayoutText.kt index f267a7a8..4f37eef5 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayoutText.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayoutText.kt @@ -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, diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayoutTextParagraph.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayoutTextParagraph.kt index 7314cc8a..691a5f66 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayoutTextParagraph.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderLayoutTextParagraph.kt @@ -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 ) ) } diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScaffold.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScaffold.kt index 6cb4ccfe..3e5522ae 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScaffold.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScaffold.kt @@ -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, diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderSettingsBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderSettingsBottomSheet.kt index 8940d07a..93754244 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderSettingsBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderSettingsBottomSheet.kt @@ -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 ) ) } diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderTopBar.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderTopBar.kt index 030a23a6..0c4065e0 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderTopBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderTopBar.kt @@ -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 + ) + ) } ) )