GetBooksById → GetBookById(single book) + Canceling all ReaderViewModel.onEvent calls on screen quit(fixes bugs with text being translated after quitting) + Fixed: Progress resets to 0% if leave when Reader is loading + Fixed: Book text does not show correctly after loading (v1.0.1)
This commit is contained in:
parent
9dfa06973f
commit
e033294df0
5 changed files with 862 additions and 831 deletions
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ua.acclorite.book_story.domain.use_case
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetBookById @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
|
suspend fun execute(id: Int): Book? {
|
||||||
|
return repository.getBooksById(listOf(id)).firstOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,12 +22,12 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -84,8 +84,6 @@ fun ReaderScreenRoot() {
|
||||||
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
val libraryViewModel: LibraryViewModel = hiltViewModel()
|
||||||
val historyViewModel: HistoryViewModel = hiltViewModel()
|
val historyViewModel: HistoryViewModel = hiltViewModel()
|
||||||
|
|
||||||
val loading = remember { mutableStateOf(true) }
|
|
||||||
|
|
||||||
val state = viewModel.state.collectAsState()
|
val state = viewModel.state.collectAsState()
|
||||||
val mainState = mainViewModel.state.collectAsState()
|
val mainState = mainViewModel.state.collectAsState()
|
||||||
|
|
||||||
|
|
@ -119,14 +117,11 @@ fun ReaderScreenRoot() {
|
||||||
it.asString(context),
|
it.asString(context),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
},
|
|
||||||
onLoaded = {
|
|
||||||
loading.value = false
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LaunchedEffect(canScroll) {
|
LaunchedEffect(canScroll) {
|
||||||
if (!canScroll && !state.value.showMenu && !loading.value) {
|
if (!canScroll && !state.value.showMenu && !state.value.loading) {
|
||||||
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
viewModel.onEvent(ReaderEvent.OnShowHideMenu(context = context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,16 +130,20 @@ fun ReaderScreenRoot() {
|
||||||
firstVisibleItemIndex = firstVisibleItemIndex.value,
|
firstVisibleItemIndex = firstVisibleItemIndex.value,
|
||||||
firstVisibleItemOffset = firstVisibleItemOffset.value,
|
firstVisibleItemOffset = firstVisibleItemOffset.value,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
isReaderLoading = loading.value,
|
|
||||||
onLibraryEvent = libraryViewModel::onEvent,
|
onLibraryEvent = libraryViewModel::onEvent,
|
||||||
onHistoryEvent = historyViewModel::onEvent
|
onHistoryEvent = historyViewModel::onEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
viewModel.clearViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ReaderScreen(
|
ReaderScreen(
|
||||||
state = state,
|
state = state,
|
||||||
mainState = mainState,
|
mainState = mainState,
|
||||||
loading = loading,
|
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
onEvent = viewModel::onEvent,
|
onEvent = viewModel::onEvent,
|
||||||
onMainEvent = mainViewModel::onEvent,
|
onMainEvent = mainViewModel::onEvent,
|
||||||
|
|
@ -158,7 +157,6 @@ fun ReaderScreenRoot() {
|
||||||
private fun ReaderScreen(
|
private fun ReaderScreen(
|
||||||
state: State<ReaderState>,
|
state: State<ReaderState>,
|
||||||
mainState: State<MainState>,
|
mainState: State<MainState>,
|
||||||
loading: State<Boolean>,
|
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
onEvent: (ReaderEvent) -> Unit,
|
onEvent: (ReaderEvent) -> Unit,
|
||||||
onMainEvent: (MainEvent) -> Unit,
|
onMainEvent: (MainEvent) -> Unit,
|
||||||
|
|
@ -355,7 +353,7 @@ private fun ReaderScreen(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
.then(
|
.then(
|
||||||
if (!loading.value && toolbarHidden) {
|
if (!state.value.loading && toolbarHidden) {
|
||||||
Modifier
|
Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
|
|
@ -372,7 +370,6 @@ private fun ReaderScreen(
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(paragraphHeight)
|
verticalArrangement = Arrangement.spacedBy(paragraphHeight)
|
||||||
) {
|
) {
|
||||||
if (state.value.text.isNotEmpty()) {
|
|
||||||
item {
|
item {
|
||||||
DisableSelection {
|
DisableSelection {
|
||||||
ReaderStartItem(state = state)
|
ReaderStartItem(state = state)
|
||||||
|
|
@ -380,7 +377,6 @@ private fun ReaderScreen(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(18.dp))
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
customItems(
|
customItems(
|
||||||
text, key = { key -> key.first }
|
text, key = { key -> key.first }
|
||||||
|
|
@ -403,7 +399,6 @@ private fun ReaderScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.value.text.isNotEmpty()) {
|
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(18.dp))
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
|
|
||||||
|
|
@ -419,9 +414,8 @@ private fun ReaderScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (loading.value || state.value.errorMessage != null) {
|
if (state.value.loading || state.value.errorMessage != null) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -429,7 +423,7 @@ private fun ReaderScreen(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
if (!loading.value) {
|
if (!state.value.loading) {
|
||||||
IsError(
|
IsError(
|
||||||
errorMessage = state.value.errorMessage!!.asString(),
|
errorMessage = state.value.errorMessage!!.asString(),
|
||||||
icon = painterResource(id = R.drawable.error),
|
icon = painterResource(id = R.drawable.error),
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,9 @@ import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class ReaderEvent {
|
sealed class ReaderEvent {
|
||||||
data class OnTextIsEmpty(val onLoaded: () -> Unit) : ReaderEvent()
|
data object OnTextIsEmpty : ReaderEvent()
|
||||||
data class OnLoadText(
|
data class OnLoadText(
|
||||||
val refreshList: (Book) -> Unit,
|
val refreshList: (Book) -> Unit,
|
||||||
val onLoaded: () -> Unit,
|
|
||||||
val onError: (UIText) -> Unit,
|
val onError: (UIText) -> Unit,
|
||||||
val onTextIsEmpty: () -> Unit
|
val onTextIsEmpty: () -> Unit
|
||||||
) : ReaderEvent()
|
) : ReaderEvent()
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ data class ReaderState(
|
||||||
val isTranslating: Boolean = false,
|
val isTranslating: Boolean = false,
|
||||||
|
|
||||||
val errorMessage: UIText? = null,
|
val errorMessage: UIText? = null,
|
||||||
|
val loading: Boolean = true,
|
||||||
|
|
||||||
val showMenu: Boolean = false,
|
val showMenu: Boolean = false,
|
||||||
val lockMenu: Boolean = false,
|
val lockMenu: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
@ -31,7 +32,7 @@ import ua.acclorite.book_story.domain.model.LineWithTranslation
|
||||||
import ua.acclorite.book_story.domain.model.SelectableLanguage
|
import ua.acclorite.book_story.domain.model.SelectableLanguage
|
||||||
import ua.acclorite.book_story.domain.use_case.DeleteLanguageModel
|
import ua.acclorite.book_story.domain.use_case.DeleteLanguageModel
|
||||||
import ua.acclorite.book_story.domain.use_case.DownloadLanguageModel
|
import ua.acclorite.book_story.domain.use_case.DownloadLanguageModel
|
||||||
import ua.acclorite.book_story.domain.use_case.GetBooksById
|
import ua.acclorite.book_story.domain.use_case.GetBookById
|
||||||
import ua.acclorite.book_story.domain.use_case.GetLatestHistory
|
import ua.acclorite.book_story.domain.use_case.GetLatestHistory
|
||||||
import ua.acclorite.book_story.domain.use_case.GetText
|
import ua.acclorite.book_story.domain.use_case.GetText
|
||||||
import ua.acclorite.book_story.domain.use_case.IdentifyLanguage
|
import ua.acclorite.book_story.domain.use_case.IdentifyLanguage
|
||||||
|
|
@ -42,7 +43,6 @@ import ua.acclorite.book_story.domain.util.ID
|
||||||
import ua.acclorite.book_story.domain.util.LanguageCode
|
import ua.acclorite.book_story.domain.util.LanguageCode
|
||||||
import ua.acclorite.book_story.domain.util.Resource
|
import ua.acclorite.book_story.domain.util.Resource
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import ua.acclorite.book_story.presentation.data.Argument
|
|
||||||
import ua.acclorite.book_story.presentation.data.Navigator
|
import ua.acclorite.book_story.presentation.data.Navigator
|
||||||
import ua.acclorite.book_story.presentation.data.Screen
|
import ua.acclorite.book_story.presentation.data.Screen
|
||||||
import ua.acclorite.book_story.presentation.data.removeKey
|
import ua.acclorite.book_story.presentation.data.removeKey
|
||||||
|
|
@ -61,7 +61,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
private val updateBooks: UpdateBooks,
|
private val updateBooks: UpdateBooks,
|
||||||
private val getText: GetText,
|
private val getText: GetText,
|
||||||
private val getLatestHistory: GetLatestHistory,
|
private val getLatestHistory: GetLatestHistory,
|
||||||
private val getBooksById: GetBooksById,
|
private val getBookById: GetBookById,
|
||||||
private val identifyLanguage: IdentifyLanguage,
|
private val identifyLanguage: IdentifyLanguage,
|
||||||
private val isLanguageModelDownloaded: IsLanguageModelDownloaded,
|
private val isLanguageModelDownloaded: IsLanguageModelDownloaded,
|
||||||
private val downloadLanguageModel: DownloadLanguageModel,
|
private val downloadLanguageModel: DownloadLanguageModel,
|
||||||
|
|
@ -72,6 +72,8 @@ class ReaderViewModel @Inject constructor(
|
||||||
private val _state = MutableStateFlow(ReaderState())
|
private val _state = MutableStateFlow(ReaderState())
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
private var eventJob = SupervisorJob()
|
||||||
|
|
||||||
private var dismissTranslatorJob: Job? = null
|
private var dismissTranslatorJob: Job? = null
|
||||||
private var cancelTranslationJob: Job? = null
|
private var cancelTranslationJob: Job? = null
|
||||||
private var translatorJob: Job? = null
|
private var translatorJob: Job? = null
|
||||||
|
|
@ -79,21 +81,18 @@ class ReaderViewModel @Inject constructor(
|
||||||
private var downloadLanguageJob: Job? = null
|
private var downloadLanguageJob: Job? = null
|
||||||
|
|
||||||
fun onEvent(event: ReaderEvent) {
|
fun onEvent(event: ReaderEvent) {
|
||||||
|
viewModelScope.launch(eventJob + Dispatchers.Main) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is ReaderEvent.OnTextIsEmpty -> {
|
is ReaderEvent.OnTextIsEmpty -> {
|
||||||
_state.update {
|
_state.update {
|
||||||
event.onLoaded()
|
|
||||||
it.copy(
|
it.copy(
|
||||||
errorMessage = UIText.StringResource(R.string.error_no_text),
|
loading = false,
|
||||||
|
errorMessage = UIText.StringResource(R.string.error_no_text)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnLoadText -> {
|
is ReaderEvent.OnLoadText -> {
|
||||||
viewModelScope.launch {
|
|
||||||
cancelJobs()
|
|
||||||
joinJobs()
|
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val text = getText.execute(_state.value.book.textPath)
|
val text = getText.execute(_state.value.book.textPath)
|
||||||
|
|
||||||
|
|
@ -142,7 +141,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
event.refreshList(_state.value.book)
|
event.refreshList(_state.value.book)
|
||||||
|
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
snapshotFlow {
|
snapshotFlow {
|
||||||
_state.value.listState.layoutInfo.totalItemsCount
|
_state.value.listState.layoutInfo.totalItemsCount
|
||||||
}.collectLatest { itemsCount ->
|
}.collectLatest { itemsCount ->
|
||||||
|
|
@ -154,7 +153,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
var loaded = false
|
var loaded = false
|
||||||
for (i in 1..100) {
|
for (i in 1..100) {
|
||||||
try {
|
try {
|
||||||
_state.value.listState.scrollToItem(
|
_state.value.listState.requestScrollToItem(
|
||||||
index,
|
index,
|
||||||
offset
|
offset
|
||||||
)
|
)
|
||||||
|
|
@ -175,7 +174,11 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(100)
|
delay(100)
|
||||||
event.onLoaded()
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
loading = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
_state.value.book.translateWhenOpen &&
|
_state.value.book.translateWhenOpen &&
|
||||||
|
|
@ -194,11 +197,10 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is ReaderEvent.OnShowHideMenu -> {
|
is ReaderEvent.OnShowHideMenu -> {
|
||||||
if (_state.value.lockMenu) {
|
if (_state.value.lockMenu) {
|
||||||
return
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val shouldShow = event.show ?: !_state.value.showMenu
|
val shouldShow = event.show ?: !_state.value.showMenu
|
||||||
|
|
@ -229,13 +231,14 @@ class ReaderViewModel @Inject constructor(
|
||||||
is ReaderEvent.OnShowHideTranslatorBottomSheet -> {
|
is ReaderEvent.OnShowHideTranslatorBottomSheet -> {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
showTranslatorBottomSheet = event.show ?: !it.showTranslatorBottomSheet
|
showTranslatorBottomSheet = event.show
|
||||||
|
?: !it.showTranslatorBottomSheet
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnChangeTranslatorSettings -> {
|
is ReaderEvent.OnChangeTranslatorSettings -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val book = _state.value.book
|
val book = _state.value.book
|
||||||
val updatedBook = book.copy(
|
val updatedBook = book.copy(
|
||||||
enableTranslator = event.enableTranslator ?: book.enableTranslator,
|
enableTranslator = event.enableTranslator ?: book.enableTranslator,
|
||||||
|
|
@ -243,7 +246,8 @@ class ReaderViewModel @Inject constructor(
|
||||||
translateTo = event.translateTo ?: book.translateTo,
|
translateTo = event.translateTo ?: book.translateTo,
|
||||||
doubleClickTranslation = event.doubleClickTranslation
|
doubleClickTranslation = event.doubleClickTranslation
|
||||||
?: book.doubleClickTranslation,
|
?: book.doubleClickTranslation,
|
||||||
translateWhenOpen = event.translateWhenOpen ?: book.translateWhenOpen
|
translateWhenOpen = event.translateWhenOpen
|
||||||
|
?: book.translateWhenOpen
|
||||||
)
|
)
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
|
|
@ -271,7 +275,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
joinJobs()
|
joinJobs()
|
||||||
|
|
||||||
translatorJob = launch(Dispatchers.IO) {
|
translatorJob = launch(Dispatchers.IO) {
|
||||||
|
|
@ -308,12 +312,14 @@ class ReaderViewModel @Inject constructor(
|
||||||
|
|
||||||
yield()
|
yield()
|
||||||
if (_state.value.book.translateFrom == "auto") {
|
if (_state.value.book.translateFrom == "auto") {
|
||||||
val identifiedLanguages = mutableListOf<Pair<LanguageCode, ID>>()
|
val identifiedLanguages =
|
||||||
|
mutableListOf<Pair<LanguageCode, ID>>()
|
||||||
val errors = mutableMapOf<ID, LineWithTranslation>()
|
val errors = mutableMapOf<ID, LineWithTranslation>()
|
||||||
|
|
||||||
for (line in linesToTranslate.toMap()) {
|
for (line in linesToTranslate.toMap()) {
|
||||||
yield()
|
yield()
|
||||||
val result = identifyLanguage.execute(line.value.originalLine)
|
val result =
|
||||||
|
identifyLanguage.execute(line.value.originalLine)
|
||||||
|
|
||||||
when (result) {
|
when (result) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
|
|
@ -390,7 +396,8 @@ class ReaderViewModel @Inject constructor(
|
||||||
it.uppercase()
|
it.uppercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
val translateToCanceled = _state.value.languagesToTranslate.find {
|
val translateToCanceled =
|
||||||
|
_state.value.languagesToTranslate.find {
|
||||||
it.languageCode == _state.value.book.translateTo
|
it.languageCode == _state.value.book.translateTo
|
||||||
}?.isCanceled ?: false
|
}?.isCanceled ?: false
|
||||||
|
|
||||||
|
|
@ -420,7 +427,8 @@ class ReaderViewModel @Inject constructor(
|
||||||
it.uppercase()
|
it.uppercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
val isCanceled = _state.value.languagesToTranslate.find {
|
val isCanceled =
|
||||||
|
_state.value.languagesToTranslate.find {
|
||||||
it.languageCode == language.first
|
it.languageCode == language.first
|
||||||
}?.isCanceled ?: false
|
}?.isCanceled ?: false
|
||||||
|
|
||||||
|
|
@ -460,9 +468,11 @@ class ReaderViewModel @Inject constructor(
|
||||||
val line = linesToTranslate[closestLine] ?: continue
|
val line = linesToTranslate[closestLine] ?: continue
|
||||||
|
|
||||||
yield()
|
yield()
|
||||||
val sourceLanguage = if (_state.value.book.translateFrom == "auto") {
|
val sourceLanguage =
|
||||||
|
if (_state.value.book.translateFrom == "auto") {
|
||||||
when (
|
when (
|
||||||
val result = identifyLanguage.execute(line.originalLine)
|
val result =
|
||||||
|
identifyLanguage.execute(line.originalLine)
|
||||||
) {
|
) {
|
||||||
is Resource.Success -> result.data!!
|
is Resource.Success -> result.data!!
|
||||||
is Resource.Error -> continue
|
is Resource.Error -> continue
|
||||||
|
|
@ -556,7 +566,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
joinJobs()
|
joinJobs()
|
||||||
|
|
||||||
dismissTranslatorJob = launch(Dispatchers.IO) {
|
dismissTranslatorJob = launch(Dispatchers.IO) {
|
||||||
|
|
@ -594,7 +604,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnCancelTranslation -> {
|
is ReaderEvent.OnCancelTranslation -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
cancelTranslationJob?.join()
|
cancelTranslationJob?.join()
|
||||||
undoTranslationJob?.join()
|
undoTranslationJob?.join()
|
||||||
dismissTranslatorJob?.cancel()
|
dismissTranslatorJob?.cancel()
|
||||||
|
|
@ -627,7 +637,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnUndoTranslation -> {
|
is ReaderEvent.OnUndoTranslation -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
cancelTranslationJob?.join()
|
cancelTranslationJob?.join()
|
||||||
undoTranslationJob?.join()
|
undoTranslationJob?.join()
|
||||||
|
|
||||||
|
|
@ -670,7 +680,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnDownloadLanguages -> {
|
is ReaderEvent.OnDownloadLanguages -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
downloadLanguageJob?.cancel()
|
downloadLanguageJob?.cancel()
|
||||||
|
|
||||||
downloadLanguageJob = launch(Dispatchers.IO) {
|
downloadLanguageJob = launch(Dispatchers.IO) {
|
||||||
|
|
@ -722,7 +732,8 @@ class ReaderViewModel @Inject constructor(
|
||||||
languageCode = lang.languageCode,
|
languageCode = lang.languageCode,
|
||||||
onCompleted = {
|
onCompleted = {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val modelDownloaded = isLanguageModelDownloaded.execute(
|
val modelDownloaded =
|
||||||
|
isLanguageModelDownloaded.execute(
|
||||||
lang.languageCode
|
lang.languageCode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -828,7 +839,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnSelectLanguage -> {
|
is ReaderEvent.OnSelectLanguage -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val editedList = _state.value.languagesToTranslate.map {
|
val editedList = _state.value.languagesToTranslate.map {
|
||||||
if (it.languageCode == event.language.languageCode) {
|
if (it.languageCode == event.language.languageCode) {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
@ -848,7 +859,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnUpdateLanguage -> {
|
is ReaderEvent.OnUpdateLanguage -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val editedList = _state.value.languagesToTranslate.map {
|
val editedList = _state.value.languagesToTranslate.map {
|
||||||
if (it.languageCode == event.languageCode) {
|
if (it.languageCode == event.languageCode) {
|
||||||
event.calculation(it)
|
event.calculation(it)
|
||||||
|
|
@ -866,8 +877,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnGoBack -> {
|
is ReaderEvent.OnGoBack -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
cancelJobs()
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
lockMenu = true
|
lockMenu = true
|
||||||
|
|
@ -879,49 +889,35 @@ class ReaderViewModel @Inject constructor(
|
||||||
event.context.window.decorView
|
event.context.window.decorView
|
||||||
)
|
)
|
||||||
|
|
||||||
if (_state.value.listState.layoutInfo.totalItemsCount > 0) {
|
val listState = _state.value.listState
|
||||||
val firstVisibleItemIndex = _state.value.listState.firstVisibleItemIndex
|
if (
|
||||||
val firstVisibleItemOffset =
|
!_state.value.loading &&
|
||||||
_state.value.listState.firstVisibleItemScrollOffset
|
listState.layoutInfo.totalItemsCount != 0 &&
|
||||||
val lastVisibleItemIndex = _state.value.listState.layoutInfo
|
_state.value.text.isNotEmpty()
|
||||||
.visibleItemsInfo.last().index
|
) {
|
||||||
|
|
||||||
val progress = if (firstVisibleItemIndex > 0) {
|
|
||||||
if (lastVisibleItemIndex >= (_state.value.listState.layoutInfo.totalItemsCount - 1)) {
|
|
||||||
1f
|
|
||||||
} else {
|
|
||||||
(firstVisibleItemIndex.toFloat() / (_state.value.text.size - 1)
|
|
||||||
.toFloat())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0f
|
|
||||||
}
|
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
progress = progress,
|
progress = calculateProgress(),
|
||||||
scrollIndex = firstVisibleItemIndex,
|
scrollIndex = listState.firstVisibleItemIndex,
|
||||||
scrollOffset = firstVisibleItemOffset
|
scrollOffset = listState.firstVisibleItemScrollOffset
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateBooks.execute(
|
updateBooks.execute(
|
||||||
listOf(_state.value.book)
|
listOf(_state.value.book)
|
||||||
)
|
)
|
||||||
event.navigator.putArgument(
|
|
||||||
Argument("book", _state.value.book.id)
|
|
||||||
)
|
|
||||||
event.refreshList(_state.value.book)
|
event.refreshList(_state.value.book)
|
||||||
|
}
|
||||||
|
|
||||||
insetsController.show(WindowInsetsCompat.Type.systemBars())
|
insetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||||
event.navigate(event.navigator)
|
event.navigate(event.navigator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnScroll -> {
|
is ReaderEvent.OnScroll -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
val scrollTo = (_state.value.text.size * event.progress).roundToInt()
|
val scrollTo = (_state.value.text.size * event.progress).roundToInt()
|
||||||
|
|
||||||
_state.value.listState.scrollToItem(
|
_state.value.listState.scrollToItem(
|
||||||
|
|
@ -931,7 +927,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnChangeProgress -> {
|
is ReaderEvent.OnChangeProgress -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
|
|
@ -945,18 +941,12 @@ class ReaderViewModel @Inject constructor(
|
||||||
updateBooks.execute(
|
updateBooks.execute(
|
||||||
listOf(_state.value.book)
|
listOf(_state.value.book)
|
||||||
)
|
)
|
||||||
event.navigator.putArgument(
|
|
||||||
Argument(
|
|
||||||
"book",
|
|
||||||
_state.value.book.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
event.refreshList(_state.value.book)
|
event.refreshList(_state.value.book)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnShowHideSettingsBottomSheet -> {
|
is ReaderEvent.OnShowHideSettingsBottomSheet -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
currentPage = if (it.showSettingsBottomSheet) it.currentPage else 0,
|
currentPage = if (it.showSettingsBottomSheet) it.currentPage else 0,
|
||||||
|
|
@ -967,13 +957,13 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnScrollToSettingsPage -> {
|
is ReaderEvent.OnScrollToSettingsPage -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
event.pagerState.scrollToPage(event.page)
|
event.pagerState.scrollToPage(event.page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnMoveBookToAlreadyRead -> {
|
is ReaderEvent.OnMoveBookToAlreadyRead -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnGoBack(
|
ReaderEvent.OnGoBack(
|
||||||
event.context,
|
event.context,
|
||||||
|
|
@ -983,16 +973,14 @@ class ReaderViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val firstVisibleItemIndex = _state.value.listState.firstVisibleItemIndex
|
|
||||||
val firstVisibleItemOffset = _state.value.listState.firstVisibleItemScrollOffset
|
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
|
val listState = it.listState
|
||||||
it.copy(
|
it.copy(
|
||||||
book = it.book.copy(
|
book = it.book.copy(
|
||||||
category = Category.ALREADY_READ,
|
category = Category.ALREADY_READ,
|
||||||
progress = 1f,
|
progress = 1f,
|
||||||
scrollIndex = firstVisibleItemIndex,
|
scrollIndex = listState.firstVisibleItemIndex,
|
||||||
scrollOffset = firstVisibleItemOffset
|
scrollOffset = listState.firstVisibleItemScrollOffset
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1007,14 +995,12 @@ class ReaderViewModel @Inject constructor(
|
||||||
}.size - 1
|
}.size - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
cancelJobs()
|
|
||||||
|
|
||||||
event.navigator.navigate(Screen.LIBRARY, true)
|
event.navigator.navigate(Screen.LIBRARY, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnOpenTranslator -> {
|
is ReaderEvent.OnOpenTranslator -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
val translatorIntent = Intent()
|
val translatorIntent = Intent()
|
||||||
val browserIntent = Intent()
|
val browserIntent = Intent()
|
||||||
|
|
||||||
|
|
@ -1023,7 +1009,10 @@ class ReaderViewModel @Inject constructor(
|
||||||
|
|
||||||
browserIntent.action = Intent.ACTION_WEB_SEARCH
|
browserIntent.action = Intent.ACTION_WEB_SEARCH
|
||||||
|
|
||||||
translatorIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, event.textToTranslate)
|
translatorIntent.putExtra(
|
||||||
|
Intent.EXTRA_PROCESS_TEXT,
|
||||||
|
event.textToTranslate
|
||||||
|
)
|
||||||
translatorIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true)
|
translatorIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true)
|
||||||
|
|
||||||
browserIntent.putExtra(
|
browserIntent.putExtra(
|
||||||
|
|
@ -1046,7 +1035,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is ReaderEvent.OnOpenDictionary -> {
|
is ReaderEvent.OnOpenDictionary -> {
|
||||||
viewModelScope.launch {
|
launch {
|
||||||
val browserIntent = Intent()
|
val browserIntent = Intent()
|
||||||
|
|
||||||
browserIntent.action = Intent.ACTION_WEB_SEARCH
|
browserIntent.action = Intent.ACTION_WEB_SEARCH
|
||||||
|
|
@ -1066,13 +1055,6 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelJobs() {
|
|
||||||
cancelTranslationJob?.cancel()
|
|
||||||
translatorJob?.cancel()
|
|
||||||
dismissTranslatorJob?.cancel()
|
|
||||||
undoTranslationJob?.cancel()
|
|
||||||
downloadLanguageJob?.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun joinJobs() {
|
private suspend fun joinJobs() {
|
||||||
|
|
@ -1082,7 +1064,6 @@ class ReaderViewModel @Inject constructor(
|
||||||
undoTranslationJob?.join()
|
undoTranslationJob?.join()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private suspend fun findClosestLineToTranslate(
|
private suspend fun findClosestLineToTranslate(
|
||||||
translatingLines: Map<ID, LineWithTranslation>
|
translatingLines: Map<ID, LineWithTranslation>
|
||||||
): ID? {
|
): ID? {
|
||||||
|
|
@ -1248,8 +1229,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
context: ComponentActivity,
|
context: ComponentActivity,
|
||||||
refreshList: (Book) -> Unit,
|
refreshList: (Book) -> Unit,
|
||||||
onError: (UIText) -> Unit,
|
onError: (UIText) -> Unit
|
||||||
onLoaded: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val bookId = navigator.retrieveArgument("book") as? Int
|
val bookId = navigator.retrieveArgument("book") as? Int
|
||||||
|
|
@ -1259,65 +1239,66 @@ class ReaderViewModel @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val book = getBooksById.execute(listOf(bookId))
|
val book = getBookById.execute(bookId)
|
||||||
|
|
||||||
if (book.isEmpty()) {
|
if (book == null) {
|
||||||
navigator.navigateBack()
|
navigator.navigateBack()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
ReaderState(book = book.first())
|
ReaderState(book = book)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
clear()
|
||||||
|
launch {
|
||||||
onEvent(ReaderEvent.OnShowHideMenu(false, context))
|
onEvent(ReaderEvent.OnShowHideMenu(false, context))
|
||||||
}
|
|
||||||
|
|
||||||
onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnLoadText(
|
ReaderEvent.OnLoadText(
|
||||||
refreshList = { refreshList(it) },
|
refreshList = { refreshList(it) },
|
||||||
onLoaded = {
|
|
||||||
onLoaded()
|
|
||||||
},
|
|
||||||
onError = {
|
onError = {
|
||||||
onError(it)
|
onError(it)
|
||||||
},
|
},
|
||||||
onTextIsEmpty = {
|
onTextIsEmpty = {
|
||||||
onEvent(ReaderEvent.OnTextIsEmpty(onLoaded = { onLoaded() }))
|
onEvent(ReaderEvent.OnTextIsEmpty)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
suspend fun onUpdateProgress(
|
suspend fun onUpdateProgress(
|
||||||
firstVisibleItemIndex: Int,
|
firstVisibleItemIndex: Int,
|
||||||
firstVisibleItemOffset: Int,
|
firstVisibleItemOffset: Int,
|
||||||
navigator: Navigator,
|
navigator: Navigator,
|
||||||
isReaderLoading: Boolean,
|
|
||||||
onLibraryEvent: (LibraryEvent) -> Unit,
|
onLibraryEvent: (LibraryEvent) -> Unit,
|
||||||
onHistoryEvent: (HistoryEvent) -> Unit
|
onHistoryEvent: (HistoryEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
snapshotFlow {
|
snapshotFlow {
|
||||||
firstVisibleItemIndex to firstVisibleItemOffset
|
firstVisibleItemIndex to firstVisibleItemOffset
|
||||||
}.debounce(500).collectLatest { items ->
|
}
|
||||||
|
.debounce(200)
|
||||||
|
.collectLatest { items ->
|
||||||
|
val listState = _state.value.listState
|
||||||
if (
|
if (
|
||||||
!isReaderLoading &&
|
!_state.value.loading &&
|
||||||
_state.value.text.isNotEmpty() &&
|
_state.value.text.isNotEmpty() &&
|
||||||
_state.value.listState.layoutInfo.totalItemsCount > 0
|
listState.layoutInfo.totalItemsCount > 0
|
||||||
) {
|
) {
|
||||||
val lastVisibleItemIndex = _state.value.listState.layoutInfo.visibleItemsInfo
|
val lastVisibleItemIndex = listState
|
||||||
.last().index
|
.layoutInfo
|
||||||
|
.visibleItemsInfo
|
||||||
|
.last()
|
||||||
|
.index
|
||||||
|
val totalItemsCount = listState.layoutInfo.totalItemsCount - 1
|
||||||
|
|
||||||
val progress = if (items.first > 0) {
|
val progress = if (items.first > 0) {
|
||||||
if (
|
if (lastVisibleItemIndex >= totalItemsCount) {
|
||||||
lastVisibleItemIndex >= (_state.value.listState.layoutInfo.totalItemsCount - 1)
|
|
||||||
) {
|
|
||||||
1f
|
1f
|
||||||
} else {
|
} else {
|
||||||
(items.first.toFloat() / (_state.value.text.size - 1).toFloat())
|
items.first / (_state.value.text.size - 1).toFloat()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
0f
|
0f
|
||||||
|
|
@ -1338,4 +1319,48 @@ class ReaderViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun calculateProgress(): Float {
|
||||||
|
val listState = _state.value.listState
|
||||||
|
|
||||||
|
if (
|
||||||
|
_state.value.loading ||
|
||||||
|
listState.layoutInfo.totalItemsCount == 0 ||
|
||||||
|
_state.value.text.isEmpty()
|
||||||
|
) {
|
||||||
|
return _state.value.book.progress
|
||||||
|
}
|
||||||
|
|
||||||
|
val lastVisibleItemIndex = listState.layoutInfo
|
||||||
|
.visibleItemsInfo.last().index
|
||||||
|
val totalItemsCount = listState.layoutInfo.totalItemsCount - 1
|
||||||
|
|
||||||
|
if (listState.firstVisibleItemIndex == 0) {
|
||||||
|
return 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastVisibleItemIndex >= totalItemsCount) {
|
||||||
|
return 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
return listState.firstVisibleItemIndex / (_state.value.text.size - 1).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun clear() {
|
||||||
|
eventJob.cancel()
|
||||||
|
eventJob.join()
|
||||||
|
eventJob = SupervisorJob()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearViewModel() {
|
||||||
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
_state.update {
|
||||||
|
ReaderState()
|
||||||
|
}
|
||||||
|
|
||||||
|
eventJob.cancel()
|
||||||
|
eventJob.join()
|
||||||
|
eventJob = SupervisorJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue