🚀 Automatic check for text update
* Option to enable automatic check for text update when you open book in Reader * Navigates user to BookInfo screen if update is found * Shows toast(up-to-date) if corresponding setting is enabled and there is not update found
This commit is contained in:
parent
d2cfcd71a1
commit
8145cafc5a
25 changed files with 463 additions and 47 deletions
|
|
@ -37,6 +37,8 @@ object DataStoreConstants {
|
||||||
val PERCEPTION_EXPANDER = booleanPreferencesKey("perception_expander")
|
val PERCEPTION_EXPANDER = booleanPreferencesKey("perception_expander")
|
||||||
val PERCEPTION_EXPANDER_PADDING = intPreferencesKey("perception_expander_padding")
|
val PERCEPTION_EXPANDER_PADDING = intPreferencesKey("perception_expander_padding")
|
||||||
val PERCEPTION_EXPANDER_THICKNESS = intPreferencesKey("perception_expander_thickness")
|
val PERCEPTION_EXPANDER_THICKNESS = intPreferencesKey("perception_expander_thickness")
|
||||||
|
val CHECK_FOR_TEXT_UPDATE = booleanPreferencesKey("check_for_text_update")
|
||||||
|
val CHECK_FOR_TEXT_UPDATE_TOAST = booleanPreferencesKey("check_for_text_update_toast")
|
||||||
|
|
||||||
// Browse settings
|
// Browse settings
|
||||||
val BROWSE_FILES_STRUCTURE = stringPreferencesKey("browse_files_structure")
|
val BROWSE_FILES_STRUCTURE = stringPreferencesKey("browse_files_structure")
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,8 @@ sealed class Screen : Parcelable {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@Keep
|
@Keep
|
||||||
data class BookInfo(
|
data class BookInfo(
|
||||||
val bookId: Int
|
val bookId: Int,
|
||||||
|
val startUpdate: Boolean
|
||||||
) : Screen()
|
) : Screen()
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,6 @@ sealed class MainEvent {
|
||||||
data class OnChangePerceptionExpander(val bool: Boolean) : MainEvent()
|
data class OnChangePerceptionExpander(val bool: Boolean) : MainEvent()
|
||||||
data class OnChangePerceptionExpanderPadding(val padding: Int) : MainEvent()
|
data class OnChangePerceptionExpanderPadding(val padding: Int) : MainEvent()
|
||||||
data class OnChangePerceptionExpanderThickness(val thickness: Int) : MainEvent()
|
data class OnChangePerceptionExpanderThickness(val thickness: Int) : MainEvent()
|
||||||
|
data class OnChangeCheckForTextUpdate(val bool: Boolean) : MainEvent()
|
||||||
|
data class OnChangeCheckForTextUpdateToast(val bool: Boolean) : MainEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +77,8 @@ data class MainState(
|
||||||
val perceptionExpander: Boolean = provideDefaultValue { false },
|
val perceptionExpander: Boolean = provideDefaultValue { false },
|
||||||
val perceptionExpanderPadding: Int = provideDefaultValue { 5 },
|
val perceptionExpanderPadding: Int = provideDefaultValue { 5 },
|
||||||
val perceptionExpanderThickness: Int = provideDefaultValue { 4 },
|
val perceptionExpanderThickness: Int = provideDefaultValue { 4 },
|
||||||
|
val checkForTextUpdate: Boolean = provideDefaultValue { true },
|
||||||
|
val checkForTextUpdateToast: Boolean = provideDefaultValue { true },
|
||||||
|
|
||||||
// Browse Settings
|
// Browse Settings
|
||||||
val browseFilesStructure: BrowseFilesStructure = provideDefaultValue {
|
val browseFilesStructure: BrowseFilesStructure = provideDefaultValue {
|
||||||
|
|
@ -254,6 +256,14 @@ data class MainState(
|
||||||
perceptionExpanderThickness = provideValue(
|
perceptionExpanderThickness = provideValue(
|
||||||
PERCEPTION_EXPANDER_THICKNESS
|
PERCEPTION_EXPANDER_THICKNESS
|
||||||
) { perceptionExpanderThickness },
|
) { perceptionExpanderThickness },
|
||||||
|
|
||||||
|
checkForTextUpdate = provideValue(
|
||||||
|
CHECK_FOR_TEXT_UPDATE
|
||||||
|
) { checkForTextUpdate },
|
||||||
|
|
||||||
|
checkForTextUpdateToast = provideValue(
|
||||||
|
CHECK_FOR_TEXT_UPDATE_TOAST
|
||||||
|
) { checkForTextUpdateToast },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -502,6 +502,34 @@ class MainViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeCheckForTextUpdate -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(
|
||||||
|
DataStoreConstants.CHECK_FOR_TEXT_UPDATE,
|
||||||
|
event.bool
|
||||||
|
)
|
||||||
|
updateStateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
checkForTextUpdate = event.bool
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainEvent.OnChangeCheckForTextUpdateToast -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
setDatastore.execute(
|
||||||
|
DataStoreConstants.CHECK_FOR_TEXT_UPDATE_TOAST,
|
||||||
|
event.bool
|
||||||
|
)
|
||||||
|
updateStateWithSavedHandle {
|
||||||
|
it.copy(
|
||||||
|
checkForTextUpdateToast = event.bool
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ import ua.acclorite.book_story.presentation.screens.book_info.components.BookInf
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoStatisticSection
|
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoStatisticSection
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoTopBar
|
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoTopBar
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.change_cover_bottom_sheet.BookInfoChangeCoverBottomSheet
|
import ua.acclorite.book_story.presentation.screens.book_info.components.change_cover_bottom_sheet.BookInfoChangeCoverBottomSheet
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.confirm_update_dialog.BookInfoConfirmUpdateDialog
|
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.details_bottom_sheet.BookInfoDetailsBottomSheet
|
import ua.acclorite.book_story.presentation.screens.book_info.components.details_bottom_sheet.BookInfoDetailsBottomSheet
|
||||||
|
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoConfirmUpdateDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoDeleteDialog
|
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoDeleteDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoMoveDialog
|
import ua.acclorite.book_story.presentation.screens.book_info.components.dialog.BookInfoMoveDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
|
|
@ -65,15 +65,22 @@ import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||||
fun BookInfoScreenRoot(screen: Screen.BookInfo) {
|
fun BookInfoScreenRoot(screen: Screen.BookInfo) {
|
||||||
val viewModel = LocalBookInfoViewModel.current.viewModel
|
val viewModel = LocalBookInfoViewModel.current.viewModel
|
||||||
val onNavigate = LocalOnNavigate.current
|
val onNavigate = LocalOnNavigate.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val snackbarState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.init(
|
viewModel.init(
|
||||||
screen = screen,
|
screen = screen,
|
||||||
onNavigate = onNavigate
|
snackbarState = snackbarState,
|
||||||
|
onNavigate = onNavigate,
|
||||||
|
context = context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BookInfoScreen()
|
BookInfoScreen(
|
||||||
|
snackbarState = snackbarState
|
||||||
|
)
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
|
|
@ -84,19 +91,18 @@ fun BookInfoScreenRoot(screen: Screen.BookInfo) {
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun BookInfoScreen() {
|
private fun BookInfoScreen(snackbarState: SnackbarHostState) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val state = LocalBookInfoViewModel.current.state
|
val state = LocalBookInfoViewModel.current.state
|
||||||
val onEvent = LocalBookInfoViewModel.current.onEvent
|
val onEvent = LocalBookInfoViewModel.current.onEvent
|
||||||
val onNavigate = LocalOnNavigate.current
|
val onNavigate = LocalOnNavigate.current
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val snackbarState = remember { SnackbarHostState() }
|
|
||||||
val refreshState = rememberPullRefreshState(
|
val refreshState = rememberPullRefreshState(
|
||||||
refreshing = state.value.isRefreshing || state.value.isLoadingUpdate,
|
refreshing = state.value.updating || state.value.checkingForUpdate,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnCheckForUpdate(
|
||||||
snackbarState,
|
snackbarState,
|
||||||
context
|
context
|
||||||
)
|
)
|
||||||
|
|
@ -181,7 +187,7 @@ private fun BookInfoScreen() {
|
||||||
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!state.value.isRefreshing) {
|
if (!state.value.updating) {
|
||||||
onEvent(BookInfoEvent.OnNavigateToReaderScreen(onNavigate = onNavigate))
|
onEvent(BookInfoEvent.OnNavigateToReaderScreen(onNavigate = onNavigate))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -221,7 +227,7 @@ private fun BookInfoScreen() {
|
||||||
)
|
)
|
||||||
|
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
state.value.isRefreshing || state.value.isLoadingUpdate,
|
state.value.updating || state.value.checkingForUpdate,
|
||||||
refreshState,
|
refreshState,
|
||||||
Modifier
|
Modifier
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
|
|
@ -254,7 +260,7 @@ private fun BookInfoScreen() {
|
||||||
return@BackHandler
|
return@BackHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.value.isRefreshing) {
|
if (!state.value.updating) {
|
||||||
onEvent(BookInfoEvent.OnCancelUpdate)
|
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
onNavigate {
|
onNavigate {
|
||||||
navigateBack()
|
navigateBack()
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ fun BookInfoMoreDropDown(snackbarState: SnackbarHostState) {
|
||||||
contentDescription = R.string.show_dropdown_content_desc,
|
contentDescription = R.string.show_dropdown_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !showDropDown &&
|
enabled = !showDropDown &&
|
||||||
!state.value.isRefreshing
|
!state.value.updating
|
||||||
) {
|
) {
|
||||||
showDropDown = true
|
showDropDown = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ fun BookInfoTopBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GoBackButton(onNavigate = onNavigate, enabled = !state.value.isRefreshing) {
|
GoBackButton(onNavigate = onNavigate, enabled = !state.value.updating) {
|
||||||
onEvent(BookInfoEvent.OnCancelUpdate)
|
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,10 +106,10 @@ fun BookInfoTopBar(
|
||||||
icon = Icons.Outlined.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
contentDescription = R.string.refresh_book_content_desc,
|
contentDescription = R.string.refresh_book_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
enabled = !state.value.isRefreshing && !state.value.isLoadingUpdate
|
enabled = !state.value.updating && !state.value.checkingForUpdate
|
||||||
) {
|
) {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnCheckForUpdate(
|
||||||
snackbarState,
|
snackbarState,
|
||||||
context
|
context
|
||||||
)
|
)
|
||||||
|
|
@ -135,7 +135,7 @@ fun BookInfoTopBar(
|
||||||
icon = Icons.Outlined.Done,
|
icon = Icons.Outlined.Done,
|
||||||
contentDescription = R.string.apply_changes_content_desc,
|
contentDescription = R.string.apply_changes_content_desc,
|
||||||
disableOnClick = true,
|
disableOnClick = true,
|
||||||
enabled = !state.value.isRefreshing &&
|
enabled = !state.value.updating &&
|
||||||
(state.value.titleValue.isNotBlank() &&
|
(state.value.titleValue.isNotBlank() &&
|
||||||
state.value.titleValue.trim() !=
|
state.value.titleValue.trim() !=
|
||||||
state.value.book.title.trim()) ||
|
state.value.book.title.trim()) ||
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ua.acclorite.book_story.presentation.screens.book_info.components.confirm_update_dialog
|
package ua.acclorite.book_story.presentation.screens.book_info.components.dialog
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Update
|
import androidx.compose.material.icons.filled.Update
|
||||||
|
|
@ -101,7 +101,7 @@ sealed class BookInfoEvent {
|
||||||
val snackbarState: SnackbarHostState
|
val snackbarState: SnackbarHostState
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
||||||
data class OnLoadUpdate(
|
data class OnCheckForUpdate(
|
||||||
val snackbarState: SnackbarHostState,
|
val snackbarState: SnackbarHostState,
|
||||||
val context: Context
|
val context: Context
|
||||||
) : BookInfoEvent()
|
) : BookInfoEvent()
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import ua.acclorite.book_story.presentation.core.constants.Constants
|
||||||
data class BookInfoState(
|
data class BookInfoState(
|
||||||
val book: Book = Constants.EMPTY_BOOK,
|
val book: Book = Constants.EMPTY_BOOK,
|
||||||
|
|
||||||
val isLoadingUpdate: Boolean = false,
|
val checkingForUpdate: Boolean = false,
|
||||||
val isRefreshing: Boolean = false,
|
val updating: Boolean = false,
|
||||||
|
|
||||||
val showConfirmUpdateDialog: Boolean = false,
|
val showConfirmUpdateDialog: Boolean = false,
|
||||||
val updatedText: List<String>? = null,
|
val updatedText: List<String>? = null,
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ package ua.acclorite.book_story.presentation.screens.book_info.data
|
||||||
|
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
import android.content.Context.CLIPBOARD_SERVICE
|
import android.content.Context.CLIPBOARD_SERVICE
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.SnackbarResult
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
|
@ -451,13 +453,13 @@ class BookInfoViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is BookInfoEvent.OnLoadUpdate -> {
|
is BookInfoEvent.OnCheckForUpdate -> {
|
||||||
updateJob?.cancel()
|
updateJob?.cancel()
|
||||||
updateJob = launch(Dispatchers.IO) {
|
updateJob = launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
showConfirmUpdateDialog = false,
|
showConfirmUpdateDialog = false,
|
||||||
isLoadingUpdate = true,
|
checkingForUpdate = true,
|
||||||
editTitle = false,
|
editTitle = false,
|
||||||
editAuthor = false,
|
editAuthor = false,
|
||||||
editDescription = false
|
editDescription = false
|
||||||
|
|
@ -477,7 +479,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
action = event.context.getString(R.string.retry),
|
action = event.context.getString(R.string.retry),
|
||||||
onAction = {
|
onAction = {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnCheckForUpdate(
|
||||||
snackbarState = event.snackbarState,
|
snackbarState = event.snackbarState,
|
||||||
context = event.context
|
context = event.context
|
||||||
)
|
)
|
||||||
|
|
@ -491,7 +493,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoadingUpdate = false
|
checkingForUpdate = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
|
|
@ -508,7 +510,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
action = event.context.getString(R.string.retry),
|
action = event.context.getString(R.string.retry),
|
||||||
onAction = {
|
onAction = {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnCheckForUpdate(
|
||||||
snackbarState = event.snackbarState,
|
snackbarState = event.snackbarState,
|
||||||
context = event.context
|
context = event.context
|
||||||
)
|
)
|
||||||
|
|
@ -522,7 +524,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoadingUpdate = false
|
checkingForUpdate = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
|
|
@ -554,7 +556,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoadingUpdate = false
|
checkingForUpdate = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
|
|
@ -571,7 +573,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoadingUpdate = false
|
checkingForUpdate = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -601,7 +603,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = true,
|
updating = true,
|
||||||
showConfirmUpdateDialog = false
|
showConfirmUpdateDialog = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -615,7 +617,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
action = event.context.getString(R.string.retry),
|
action = event.context.getString(R.string.retry),
|
||||||
onAction = {
|
onAction = {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnCheckForUpdate(
|
||||||
snackbarState = event.snackbarState,
|
snackbarState = event.snackbarState,
|
||||||
context = event.context
|
context = event.context
|
||||||
)
|
)
|
||||||
|
|
@ -629,7 +631,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false,
|
updating = false,
|
||||||
updatedText = null,
|
updatedText = null,
|
||||||
updatedChapters = null
|
updatedChapters = null
|
||||||
)
|
)
|
||||||
|
|
@ -660,7 +662,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
action = event.context.getString(R.string.retry),
|
action = event.context.getString(R.string.retry),
|
||||||
onAction = {
|
onAction = {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnCheckForUpdate(
|
||||||
snackbarState = event.snackbarState,
|
snackbarState = event.snackbarState,
|
||||||
context = event.context
|
context = event.context
|
||||||
)
|
)
|
||||||
|
|
@ -674,7 +676,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false,
|
updating = false,
|
||||||
updatedText = null,
|
updatedText = null,
|
||||||
updatedChapters = null
|
updatedChapters = null
|
||||||
)
|
)
|
||||||
|
|
@ -692,7 +694,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
action = event.context.getString(R.string.retry),
|
action = event.context.getString(R.string.retry),
|
||||||
onAction = {
|
onAction = {
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnLoadUpdate(
|
BookInfoEvent.OnCheckForUpdate(
|
||||||
snackbarState = event.snackbarState,
|
snackbarState = event.snackbarState,
|
||||||
context = event.context
|
context = event.context
|
||||||
)
|
)
|
||||||
|
|
@ -706,7 +708,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false,
|
updating = false,
|
||||||
updatedText = null,
|
updatedText = null,
|
||||||
updatedChapters = null
|
updatedChapters = null
|
||||||
)
|
)
|
||||||
|
|
@ -735,7 +737,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
delay(500)
|
delay(500)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isRefreshing = false
|
updating = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -748,7 +750,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
|
|
||||||
it.copy(
|
it.copy(
|
||||||
showConfirmUpdateDialog = false,
|
showConfirmUpdateDialog = false,
|
||||||
isLoadingUpdate = false,
|
checkingForUpdate = false,
|
||||||
updatedText = null,
|
updatedText = null,
|
||||||
updatedChapters = null
|
updatedChapters = null
|
||||||
)
|
)
|
||||||
|
|
@ -778,7 +780,12 @@ class BookInfoViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(screen: Screen.BookInfo, onNavigate: OnNavigate) {
|
fun init(
|
||||||
|
screen: Screen.BookInfo,
|
||||||
|
snackbarState: SnackbarHostState,
|
||||||
|
context: Context,
|
||||||
|
onNavigate: OnNavigate
|
||||||
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val book = getBookById.execute(screen.bookId)
|
val book = getBookById.execute(screen.bookId)
|
||||||
|
|
||||||
|
|
@ -796,6 +803,22 @@ class BookInfoViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
|
if (screen.startUpdate) {
|
||||||
|
onEvent(
|
||||||
|
BookInfoEvent.OnCheckForUpdate(
|
||||||
|
snackbarState = snackbarState,
|
||||||
|
context = context
|
||||||
|
)
|
||||||
|
)
|
||||||
|
onNavigate {
|
||||||
|
putScreen(
|
||||||
|
Screen.BookInfo(
|
||||||
|
screen.bookId,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
onEvent(BookInfoEvent.OnCheckCoverReset)
|
onEvent(BookInfoEvent.OnCheckCoverReset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,10 @@ private fun HistoryScreen() {
|
||||||
onBodyClick = {
|
onBodyClick = {
|
||||||
onNavigate {
|
onNavigate {
|
||||||
navigate(
|
navigate(
|
||||||
Screen.BookInfo(it.bookId)
|
Screen.BookInfo(
|
||||||
|
bookId = it.bookId,
|
||||||
|
startUpdate = false
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,12 @@ private fun LibraryScreen(
|
||||||
onEvent(LibraryEvent.OnSelectBook(it))
|
onEvent(LibraryEvent.OnSelectBook(it))
|
||||||
} else {
|
} else {
|
||||||
onNavigate {
|
onNavigate {
|
||||||
navigate(Screen.BookInfo(it.first.id))
|
navigate(
|
||||||
|
Screen.BookInfo(
|
||||||
|
bookId = it.first.id,
|
||||||
|
startUpdate = false
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ import ua.acclorite.book_story.presentation.screens.reader.components.ReaderChap
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderChaptersDrawer
|
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderChaptersDrawer
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderPerceptionExpander
|
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderPerceptionExpander
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderTextParagraph
|
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderTextParagraph
|
||||||
|
import ua.acclorite.book_story.presentation.screens.reader.components.ReaderUpdateDialog
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderBottomBar
|
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderBottomBar
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderTopBar
|
import ua.acclorite.book_story.presentation.screens.reader.components.app_bar.ReaderTopBar
|
||||||
import ua.acclorite.book_story.presentation.screens.reader.components.readerFastColorPresetChange
|
import ua.acclorite.book_story.presentation.screens.reader.components.readerFastColorPresetChange
|
||||||
|
|
@ -117,6 +118,15 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
|
||||||
screen = screen,
|
screen = screen,
|
||||||
onNavigate = onNavigate,
|
onNavigate = onNavigate,
|
||||||
fullscreenMode = mainState.value.fullscreen,
|
fullscreenMode = mainState.value.fullscreen,
|
||||||
|
checkForTextUpdate = mainState.value.checkForTextUpdate,
|
||||||
|
checkForTextUpdateToast = {
|
||||||
|
if (mainState.value.checkForTextUpdateToast) {
|
||||||
|
context.getString(R.string.nothing_changed).showToast(
|
||||||
|
context = context,
|
||||||
|
longToast = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
activity = context,
|
activity = context,
|
||||||
refreshList = {
|
refreshList = {
|
||||||
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
||||||
|
|
@ -350,6 +360,9 @@ private fun ReaderScreen(lazyListState: LazyListState) {
|
||||||
if (state.value.showSettingsBottomSheet) {
|
if (state.value.showSettingsBottomSheet) {
|
||||||
ReaderSettingsBottomSheet()
|
ReaderSettingsBottomSheet()
|
||||||
}
|
}
|
||||||
|
if (state.value.showUpdateDialog) {
|
||||||
|
ReaderUpdateDialog()
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.reader.components
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Upgrade
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.core.components.LocalReaderViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.core.components.custom_dialog.CustomDialogWithContent
|
||||||
|
import ua.acclorite.book_story.presentation.core.navigation.LocalOnNavigate
|
||||||
|
import ua.acclorite.book_story.presentation.core.navigation.Screen
|
||||||
|
import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader Update Dialog.
|
||||||
|
* Navigates user to BookInfo and starts book update.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ReaderUpdateDialog() {
|
||||||
|
val state = LocalReaderViewModel.current.state
|
||||||
|
val onEvent = LocalReaderViewModel.current.onEvent
|
||||||
|
val onNavigate = LocalOnNavigate.current
|
||||||
|
|
||||||
|
CustomDialogWithContent(
|
||||||
|
title = stringResource(id = R.string.update_book),
|
||||||
|
imageVectorIcon = Icons.Rounded.Upgrade,
|
||||||
|
description = stringResource(
|
||||||
|
id = R.string.update_book_description
|
||||||
|
),
|
||||||
|
actionText = stringResource(id = R.string.proceed),
|
||||||
|
isActionEnabled = true,
|
||||||
|
onDismiss = { onEvent(ReaderEvent.OnShowHideUpdateDialog(false)) },
|
||||||
|
onAction = {
|
||||||
|
onNavigate {
|
||||||
|
navigate(
|
||||||
|
Screen.BookInfo(
|
||||||
|
bookId = state.value.book.id,
|
||||||
|
startUpdate = true
|
||||||
|
),
|
||||||
|
useBackAnimation = true,
|
||||||
|
saveInBackStack = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
withDivider = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,23 @@ package ua.acclorite.book_story.presentation.screens.reader.components.app_bar
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkHorizontally
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.basicMarquee
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material.icons.rounded.Menu
|
import androidx.compose.material.icons.rounded.Menu
|
||||||
|
import androidx.compose.material.icons.rounded.Upgrade
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -18,14 +26,17 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.core.components.CustomAnimatedVisibility
|
||||||
import ua.acclorite.book_story.presentation.core.components.CustomIconButton
|
import ua.acclorite.book_story.presentation.core.components.CustomIconButton
|
||||||
import ua.acclorite.book_story.presentation.core.components.LocalHistoryViewModel
|
import ua.acclorite.book_story.presentation.core.components.LocalHistoryViewModel
|
||||||
import ua.acclorite.book_story.presentation.core.components.LocalLibraryViewModel
|
import ua.acclorite.book_story.presentation.core.components.LocalLibraryViewModel
|
||||||
|
|
@ -92,8 +103,10 @@ fun ReaderTopBar() {
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
lineHeight = 20.sp,
|
lineHeight = 20.sp,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
.noRippleClickable(
|
.noRippleClickable(
|
||||||
enabled = !state.value.lockMenu,
|
enabled = !state.value.lockMenu,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -107,7 +120,10 @@ fun ReaderTopBar() {
|
||||||
navigate = {
|
navigate = {
|
||||||
onNavigate {
|
onNavigate {
|
||||||
navigate(
|
navigate(
|
||||||
Screen.BookInfo(state.value.book.id),
|
Screen.BookInfo(
|
||||||
|
bookId = state.value.book.id,
|
||||||
|
startUpdate = false
|
||||||
|
),
|
||||||
useBackAnimation = true,
|
useBackAnimation = true,
|
||||||
saveInBackStack = false
|
saveInBackStack = false
|
||||||
)
|
)
|
||||||
|
|
@ -117,8 +133,9 @@ fun ReaderTopBar() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.basicMarquee(
|
.then(
|
||||||
iterations = Int.MAX_VALUE
|
if (state.value.checkingForUpdate || state.value.updateFound) Modifier
|
||||||
|
else Modifier.basicMarquee(iterations = Int.MAX_VALUE)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -132,6 +149,40 @@ fun ReaderTopBar() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
|
CustomAnimatedVisibility(
|
||||||
|
visible = state.value.checkingForUpdate || state.value.updateFound,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = shrinkHorizontally() + fadeOut()
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (state.value.updateFound) {
|
||||||
|
CustomIconButton(
|
||||||
|
icon = Icons.Rounded.Upgrade,
|
||||||
|
contentDescription = R.string.update_text_content_desc,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
disableOnClick = false,
|
||||||
|
enabled = !state.value.lockMenu &&
|
||||||
|
state.value.updateFound &&
|
||||||
|
!state.value.checkingForUpdate
|
||||||
|
) {
|
||||||
|
onEvent(ReaderEvent.OnShowHideUpdateDialog(true))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(19.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
onEvent(ReaderEvent.OnCancelCheckTextForUpdate)
|
||||||
|
},
|
||||||
|
strokeWidth = 2.4.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state.value.currentChapter != null) {
|
if (state.value.currentChapter != null) {
|
||||||
CustomIconButton(
|
CustomIconButton(
|
||||||
icon = Icons.Rounded.Menu,
|
icon = Icons.Rounded.Menu,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ sealed class ReaderEvent {
|
||||||
data object OnTextIsEmpty : ReaderEvent()
|
data object OnTextIsEmpty : ReaderEvent()
|
||||||
|
|
||||||
data class OnLoadText(
|
data class OnLoadText(
|
||||||
|
val checkForUpdate: () -> Unit,
|
||||||
val refreshList: (Book) -> Unit,
|
val refreshList: (Book) -> Unit,
|
||||||
val onError: (UIText) -> Unit,
|
val onError: (UIText) -> Unit,
|
||||||
val onTextIsEmpty: () -> Unit
|
val onTextIsEmpty: () -> Unit
|
||||||
|
|
@ -86,4 +87,14 @@ sealed class ReaderEvent {
|
||||||
val context: ComponentActivity,
|
val context: ComponentActivity,
|
||||||
val noAppsFound: () -> Unit
|
val noAppsFound: () -> Unit
|
||||||
) : ReaderEvent()
|
) : ReaderEvent()
|
||||||
|
|
||||||
|
data class OnCheckTextForUpdate(
|
||||||
|
val noUpdateFound: () -> Unit
|
||||||
|
) : ReaderEvent()
|
||||||
|
|
||||||
|
data class OnShowHideUpdateDialog(
|
||||||
|
val show: Boolean
|
||||||
|
) : ReaderEvent()
|
||||||
|
|
||||||
|
data object OnCancelCheckTextForUpdate : ReaderEvent()
|
||||||
}
|
}
|
||||||
|
|
@ -24,6 +24,10 @@ data class ReaderState(
|
||||||
val checkpoint: Pair<Int, Int> = 0 to 0,
|
val checkpoint: Pair<Int, Int> = 0 to 0,
|
||||||
val lockMenu: Boolean = false,
|
val lockMenu: Boolean = false,
|
||||||
|
|
||||||
|
val checkingForUpdate: Boolean = false,
|
||||||
|
val updateFound: Boolean = false,
|
||||||
|
val showUpdateDialog: Boolean = false,
|
||||||
|
|
||||||
val showSettingsBottomSheet: Boolean = false,
|
val showSettingsBottomSheet: Boolean = false,
|
||||||
val currentPage: Int = 0,
|
val currentPage: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
@ -22,18 +22,22 @@ import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.Book
|
||||||
import ua.acclorite.book_story.domain.use_case.GetBookById
|
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.ParseText
|
||||||
import ua.acclorite.book_story.domain.use_case.UpdateBook
|
import ua.acclorite.book_story.domain.use_case.UpdateBook
|
||||||
import ua.acclorite.book_story.domain.util.OnNavigate
|
import ua.acclorite.book_story.domain.util.OnNavigate
|
||||||
|
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.core.navigation.Screen
|
import ua.acclorite.book_story.presentation.core.navigation.Screen
|
||||||
import ua.acclorite.book_story.presentation.core.util.BaseViewModel
|
import ua.acclorite.book_story.presentation.core.util.BaseViewModel
|
||||||
import ua.acclorite.book_story.presentation.core.util.launchActivity
|
import ua.acclorite.book_story.presentation.core.util.launchActivity
|
||||||
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
@ -43,7 +47,8 @@ class ReaderViewModel @Inject constructor(
|
||||||
private val updateBook: UpdateBook,
|
private val updateBook: UpdateBook,
|
||||||
private val getText: GetText,
|
private val getText: GetText,
|
||||||
private val getLatestHistory: GetLatestHistory,
|
private val getLatestHistory: GetLatestHistory,
|
||||||
private val getBookById: GetBookById
|
private val getBookById: GetBookById,
|
||||||
|
private val parseText: ParseText
|
||||||
) : BaseViewModel<ReaderState, ReaderEvent>() {
|
) : BaseViewModel<ReaderState, ReaderEvent>() {
|
||||||
|
|
||||||
private val _state = MutableStateFlow(ReaderState())
|
private val _state = MutableStateFlow(ReaderState())
|
||||||
|
|
@ -51,6 +56,7 @@ class ReaderViewModel @Inject constructor(
|
||||||
|
|
||||||
private var eventJob = SupervisorJob()
|
private var eventJob = SupervisorJob()
|
||||||
private var scrollJob: Job? = null
|
private var scrollJob: Job? = null
|
||||||
|
private var updateJob: Job? = null
|
||||||
|
|
||||||
override fun onEvent(event: ReaderEvent) {
|
override fun onEvent(event: ReaderEvent) {
|
||||||
viewModelScope.launch(eventJob + Dispatchers.Main) {
|
viewModelScope.launch(eventJob + Dispatchers.Main) {
|
||||||
|
|
@ -95,6 +101,8 @@ class ReaderViewModel @Inject constructor(
|
||||||
_state.value.book.scrollOffset
|
_state.value.book.scrollOffset
|
||||||
)
|
)
|
||||||
|
|
||||||
|
event.checkForUpdate()
|
||||||
|
|
||||||
delay(100)
|
delay(100)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
@ -399,6 +407,97 @@ class ReaderViewModel @Inject constructor(
|
||||||
event.noAppsFound()
|
event.noAppsFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnCheckTextForUpdate -> {
|
||||||
|
updateJob?.cancel()
|
||||||
|
updateJob = launch(Dispatchers.IO) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
checkingForUpdate = true,
|
||||||
|
updateFound = false,
|
||||||
|
showUpdateDialog = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
val currentBook = _state.value.book.apply {
|
||||||
|
if (!File(filePath).exists()) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
checkingForUpdate = false,
|
||||||
|
updateFound = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val updatedBook = parseText.execute(File(currentBook.filePath)).apply {
|
||||||
|
if (this is Resource.Error || data == null) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
checkingForUpdate = false,
|
||||||
|
updateFound = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}.data!!
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
val updatedText = updatedBook.map { it.text }.flatten()
|
||||||
|
val text = getText.execute(currentBook.textPath)
|
||||||
|
|
||||||
|
val updatedChapters = updatedBook.map { it.chapter }.run {
|
||||||
|
if (size < 2) return@run emptyList()
|
||||||
|
return@run this
|
||||||
|
}
|
||||||
|
val chapters = currentBook.chapters
|
||||||
|
|
||||||
|
yield()
|
||||||
|
|
||||||
|
if (updatedText == text && updatedChapters == chapters) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
event.noUpdateFound()
|
||||||
|
}
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
checkingForUpdate = false,
|
||||||
|
updateFound = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
updateFound = true,
|
||||||
|
checkingForUpdate = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onEvent(ReaderEvent.OnShowHideUpdateDialog(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnShowHideUpdateDialog -> {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
showUpdateDialog = event.show
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReaderEvent.OnCancelCheckTextForUpdate -> {
|
||||||
|
_state.update {
|
||||||
|
updateJob?.cancel()
|
||||||
|
|
||||||
|
it.copy(
|
||||||
|
showUpdateDialog = false,
|
||||||
|
checkingForUpdate = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -407,6 +506,8 @@ class ReaderViewModel @Inject constructor(
|
||||||
screen: Screen.Reader,
|
screen: Screen.Reader,
|
||||||
onNavigate: OnNavigate,
|
onNavigate: OnNavigate,
|
||||||
fullscreenMode: Boolean,
|
fullscreenMode: Boolean,
|
||||||
|
checkForTextUpdate: Boolean,
|
||||||
|
checkForTextUpdateToast: () -> Unit,
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
refreshList: (Book) -> Unit,
|
refreshList: (Book) -> Unit,
|
||||||
onError: (UIText) -> Unit
|
onError: (UIText) -> Unit
|
||||||
|
|
@ -437,6 +538,15 @@ class ReaderViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnLoadText(
|
ReaderEvent.OnLoadText(
|
||||||
|
checkForUpdate = {
|
||||||
|
if (checkForTextUpdate) {
|
||||||
|
onEvent(
|
||||||
|
ReaderEvent.OnCheckTextForUpdate {
|
||||||
|
checkForTextUpdateToast()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
refreshList = { refreshList(it) },
|
refreshList = { refreshList(it) },
|
||||||
onError = {
|
onError = {
|
||||||
onError(it)
|
onError(it)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.core.components.LocalMainViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for Text Update setting.
|
||||||
|
* If true, automatically checks whether there is book's text update.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CheckForTextUpdateSetting() {
|
||||||
|
val state = LocalMainViewModel.current.state
|
||||||
|
val onMainEvent = LocalMainViewModel.current.onEvent
|
||||||
|
|
||||||
|
SwitchWithTitle(
|
||||||
|
selected = state.value.checkForTextUpdate,
|
||||||
|
title = stringResource(id = R.string.check_for_text_update_option),
|
||||||
|
description = stringResource(id = R.string.check_for_text_update_option_desc),
|
||||||
|
onClick = {
|
||||||
|
onMainEvent(
|
||||||
|
MainEvent.OnChangeCheckForTextUpdate(
|
||||||
|
!state.value.checkForTextUpdate
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import ua.acclorite.book_story.R
|
||||||
|
import ua.acclorite.book_story.presentation.core.components.LocalMainViewModel
|
||||||
|
import ua.acclorite.book_story.presentation.data.MainEvent
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.components.SwitchWithTitle
|
||||||
|
import ua.acclorite.book_story.presentation.ui.ExpandingTransition
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for Text Update Toast setting.
|
||||||
|
* If true, shows a toast when the text is up-to-date.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CheckForTextUpdateToastSetting() {
|
||||||
|
val state = LocalMainViewModel.current.state
|
||||||
|
val onMainEvent = LocalMainViewModel.current.onEvent
|
||||||
|
|
||||||
|
ExpandingTransition(visible = state.value.checkForTextUpdate) {
|
||||||
|
SwitchWithTitle(
|
||||||
|
selected = state.value.checkForTextUpdateToast,
|
||||||
|
title = stringResource(id = R.string.check_for_text_update_toast_option),
|
||||||
|
onClick = {
|
||||||
|
onMainEvent(
|
||||||
|
MainEvent.OnChangeCheckForTextUpdateToast(
|
||||||
|
!state.value.checkForTextUpdateToast
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,8 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.components.SettingsSubcategory
|
import ua.acclorite.book_story.presentation.screens.settings.components.SettingsSubcategory
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.CheckForTextUpdateSetting
|
||||||
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.CheckForTextUpdateToastSetting
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.FullscreenSetting
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.FullscreenSetting
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.HideBarsOnFastScrollSetting
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.HideBarsOnFastScrollSetting
|
||||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.KeepScreenOnSetting
|
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.KeepScreenOnSetting
|
||||||
|
|
@ -34,6 +36,14 @@ fun LazyListScope.MiscSubcategory(
|
||||||
topPadding = topPadding,
|
topPadding = topPadding,
|
||||||
bottomPadding = bottomPadding
|
bottomPadding = bottomPadding
|
||||||
) {
|
) {
|
||||||
|
item {
|
||||||
|
CheckForTextUpdateSetting()
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
CheckForTextUpdateToastSetting()
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
FullscreenSetting()
|
FullscreenSetting()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
<string name="delete_history">Видалити історію читання?</string>
|
<string name="delete_history">Видалити історію читання?</string>
|
||||||
<string name="confirm_update">Підтвердити оновлення?</string>
|
<string name="confirm_update">Підтвердити оновлення?</string>
|
||||||
<string name="enable_check_for_updates">Увімнкути перевірку на оновлення?</string>
|
<string name="enable_check_for_updates">Увімнкути перевірку на оновлення?</string>
|
||||||
|
<string name="update_book">Оновити книгу?</string>
|
||||||
|
|
||||||
<!-- Dialog Descriptions -->
|
<!-- Dialog Descriptions -->
|
||||||
<string name="storage_permission_description">
|
<string name="storage_permission_description">
|
||||||
|
|
@ -75,6 +76,13 @@
|
||||||
з «api.github.com» кожен раз, коли застосунок запускається.
|
з «api.github.com» кожен раз, коли застосунок запускається.
|
||||||
Хочете продовжити та увімкнути «Перевіряти на оновлення»?
|
Хочете продовжити та увімкнути «Перевіряти на оновлення»?
|
||||||
</string>
|
</string>
|
||||||
|
<string name="update_book_description">
|
||||||
|
Знайдено оновлення тексту книги.
|
||||||
|
Оновлення необхідне для того, щоб забезпечити використання найновіших методів парсингу.
|
||||||
|
Це автономний процес і в більшості випадків пов\'язаний з оновленням парсеру текста.
|
||||||
|
Ваш прогрес не постраждає, якщо не відбудуться значні зміни.
|
||||||
|
Чи бажаєте ви продовжити оновлення?
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- Errors -->
|
<!-- Errors -->
|
||||||
<string name="error_permission">
|
<string name="error_permission">
|
||||||
|
|
@ -153,6 +161,7 @@
|
||||||
<string name="enable">Увімкнути</string>
|
<string name="enable">Увімкнути</string>
|
||||||
<string name="web_search">Веб-пошук</string>
|
<string name="web_search">Веб-пошук</string>
|
||||||
<string name="share">Поділитися</string>
|
<string name="share">Поділитися</string>
|
||||||
|
<string name="proceed">Продовжити</string>
|
||||||
|
|
||||||
<!-- Items -->
|
<!-- Items -->
|
||||||
<string name="files">файли</string>
|
<string name="files">файли</string>
|
||||||
|
|
@ -250,6 +259,9 @@
|
||||||
</string>
|
</string>
|
||||||
<string name="perception_expander_padding_option">Відступи лінії розширення сприйняття</string>
|
<string name="perception_expander_padding_option">Відступи лінії розширення сприйняття</string>
|
||||||
<string name="perception_expander_thickness_option">Товщина лінії розширення сприйняття</string>
|
<string name="perception_expander_thickness_option">Товщина лінії розширення сприйняття</string>
|
||||||
|
<string name="check_for_text_update_option">Перевіряти на оновлення тексту</string>
|
||||||
|
<string name="check_for_text_update_option_desc">Автоматично перевіряти чи є оновлення тексту</string>
|
||||||
|
<string name="check_for_text_update_toast_option">Показувати \"текст актуальний\" повідомлення</string>
|
||||||
|
|
||||||
<!-- Browse -->
|
<!-- Browse -->
|
||||||
<string name="browse_files_structure_option">Структура файлів</string>
|
<string name="browse_files_structure_option">Структура файлів</string>
|
||||||
|
|
@ -340,7 +352,7 @@
|
||||||
<string name="history_element_deleted">Елемент історії був успішно видалений.</string>
|
<string name="history_element_deleted">Елемент історії був успішно видалений.</string>
|
||||||
<string name="file_not_found">Файл \"%1$s\" не знайдено.</string>
|
<string name="file_not_found">Файл \"%1$s\" не знайдено.</string>
|
||||||
<string name="book_updated">Текст книги був успішно оновлений.</string>
|
<string name="book_updated">Текст книги був успішно оновлений.</string>
|
||||||
<string name="nothing_changed">Текст книги актуальний.</string>
|
<string name="nothing_changed">Текст актуальний.</string>
|
||||||
|
|
||||||
<!-- DropDown properties -->
|
<!-- DropDown properties -->
|
||||||
<string name="move_this_book">Перемістити</string>
|
<string name="move_this_book">Перемістити</string>
|
||||||
|
|
@ -606,5 +618,6 @@
|
||||||
<string name="checkpoint_back_content_desc">Чекпоінт назад</string>
|
<string name="checkpoint_back_content_desc">Чекпоінт назад</string>
|
||||||
<string name="checkpoint_forward_content_desc">Чекпоінт вперед</string>
|
<string name="checkpoint_forward_content_desc">Чекпоінт вперед</string>
|
||||||
<string name="chapters_content_desc">Розділи</string>
|
<string name="chapters_content_desc">Розділи</string>
|
||||||
|
<string name="update_text_content_desc">Оновити текст</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
<string name="delete_history">Delete reading history?</string>
|
<string name="delete_history">Delete reading history?</string>
|
||||||
<string name="confirm_update">Confirm update?</string>
|
<string name="confirm_update">Confirm update?</string>
|
||||||
<string name="enable_check_for_updates">Enable check for updates?</string>
|
<string name="enable_check_for_updates">Enable check for updates?</string>
|
||||||
|
<string name="update_book">Update book?</string>
|
||||||
|
|
||||||
<!-- Dialog Descriptions -->
|
<!-- Dialog Descriptions -->
|
||||||
<string name="storage_permission_description">
|
<string name="storage_permission_description">
|
||||||
|
|
@ -65,7 +66,7 @@
|
||||||
</string>
|
</string>
|
||||||
<string name="confirm_update_description">
|
<string name="confirm_update_description">
|
||||||
Update this book\'s text and chapters.
|
Update this book\'s text and chapters.
|
||||||
The progress should remain unchanged unless order of paragraphs was changed.
|
The progress should remain unchanged unless the order of paragraphs was changed.
|
||||||
</string>
|
</string>
|
||||||
<string name="update_app_description">
|
<string name="update_app_description">
|
||||||
New version of Book\'s Story was found.
|
New version of Book\'s Story was found.
|
||||||
|
|
@ -79,6 +80,13 @@
|
||||||
when app starts.
|
when app starts.
|
||||||
Do you want to proceed and enable «Check for updates»?
|
Do you want to proceed and enable «Check for updates»?
|
||||||
</string>
|
</string>
|
||||||
|
<string name="update_book_description">
|
||||||
|
Update of the text has been found.
|
||||||
|
Update is needed to ensure latest parser methods are used.
|
||||||
|
It is offline process.
|
||||||
|
Your progress will not be affected unless big changes occur.
|
||||||
|
Do you want to proceed with update?
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- Errors -->
|
<!-- Errors -->
|
||||||
<string name="error_permission">
|
<string name="error_permission">
|
||||||
|
|
@ -156,6 +164,7 @@
|
||||||
<string name="enable">Enable</string>
|
<string name="enable">Enable</string>
|
||||||
<string name="web_search">Web search</string>
|
<string name="web_search">Web search</string>
|
||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
|
<string name="proceed">Proceed</string>
|
||||||
|
|
||||||
<!-- Items -->
|
<!-- Items -->
|
||||||
<string name="files">files</string>
|
<string name="files">files</string>
|
||||||
|
|
@ -253,6 +262,9 @@
|
||||||
</string>
|
</string>
|
||||||
<string name="perception_expander_padding_option">Perception expander line padding</string>
|
<string name="perception_expander_padding_option">Perception expander line padding</string>
|
||||||
<string name="perception_expander_thickness_option">Perception expander line thickness</string>
|
<string name="perception_expander_thickness_option">Perception expander line thickness</string>
|
||||||
|
<string name="check_for_text_update_option">Check for text update</string>
|
||||||
|
<string name="check_for_text_update_option_desc">Automatically check whether there is text update</string>
|
||||||
|
<string name="check_for_text_update_toast_option">Show \"text is up-to-date\" toast</string>
|
||||||
|
|
||||||
<!-- Browse -->
|
<!-- Browse -->
|
||||||
<string name="browse_files_structure_option">Files structure</string>
|
<string name="browse_files_structure_option">Files structure</string>
|
||||||
|
|
@ -343,7 +355,7 @@
|
||||||
<string name="history_element_deleted">History element was successfully deleted.</string>
|
<string name="history_element_deleted">History element was successfully deleted.</string>
|
||||||
<string name="file_not_found">File \"%1$s\" was not found.</string>
|
<string name="file_not_found">File \"%1$s\" was not found.</string>
|
||||||
<string name="book_updated">Book\'s text was successfully updated.</string>
|
<string name="book_updated">Book\'s text was successfully updated.</string>
|
||||||
<string name="nothing_changed">Book\'s text is up to date.</string>
|
<string name="nothing_changed">Text is up-to-date.</string>
|
||||||
|
|
||||||
<!-- DropDown properties -->
|
<!-- DropDown properties -->
|
||||||
<string name="move_this_book">Move</string>
|
<string name="move_this_book">Move</string>
|
||||||
|
|
@ -611,5 +623,6 @@
|
||||||
<string name="checkpoint_back_content_desc">Checkpoint back</string>
|
<string name="checkpoint_back_content_desc">Checkpoint back</string>
|
||||||
<string name="checkpoint_forward_content_desc">Checkpoint forward</string>
|
<string name="checkpoint_forward_content_desc">Checkpoint forward</string>
|
||||||
<string name="chapters_content_desc">Chapters</string>
|
<string name="chapters_content_desc">Chapters</string>
|
||||||
|
<string name="update_text_content_desc">Update text</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue