🚀 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_PADDING = intPreferencesKey("perception_expander_padding")
|
||||
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
|
||||
val BROWSE_FILES_STRUCTURE = stringPreferencesKey("browse_files_structure")
|
||||
|
|
|
|||
|
|
@ -88,7 +88,8 @@ sealed class Screen : Parcelable {
|
|||
@Parcelize
|
||||
@Keep
|
||||
data class BookInfo(
|
||||
val bookId: Int
|
||||
val bookId: Int,
|
||||
val startUpdate: Boolean
|
||||
) : Screen()
|
||||
|
||||
@Parcelize
|
||||
|
|
|
|||
|
|
@ -40,4 +40,6 @@ sealed class MainEvent {
|
|||
data class OnChangePerceptionExpander(val bool: Boolean) : MainEvent()
|
||||
data class OnChangePerceptionExpanderPadding(val padding: 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 perceptionExpanderPadding: Int = provideDefaultValue { 5 },
|
||||
val perceptionExpanderThickness: Int = provideDefaultValue { 4 },
|
||||
val checkForTextUpdate: Boolean = provideDefaultValue { true },
|
||||
val checkForTextUpdateToast: Boolean = provideDefaultValue { true },
|
||||
|
||||
// Browse Settings
|
||||
val browseFilesStructure: BrowseFilesStructure = provideDefaultValue {
|
||||
|
|
@ -254,6 +256,14 @@ data class MainState(
|
|||
perceptionExpanderThickness = provideValue(
|
||||
PERCEPTION_EXPANDER_THICKNESS
|
||||
) { 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.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.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.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.BookInfoMoveDialog
|
||||
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) {
|
||||
val viewModel = LocalBookInfoViewModel.current.viewModel
|
||||
val onNavigate = LocalOnNavigate.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val snackbarState = remember { SnackbarHostState() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.init(
|
||||
screen = screen,
|
||||
onNavigate = onNavigate
|
||||
snackbarState = snackbarState,
|
||||
onNavigate = onNavigate,
|
||||
context = context
|
||||
)
|
||||
}
|
||||
|
||||
BookInfoScreen()
|
||||
BookInfoScreen(
|
||||
snackbarState = snackbarState
|
||||
)
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
|
|
@ -84,19 +91,18 @@ fun BookInfoScreenRoot(screen: Screen.BookInfo) {
|
|||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun BookInfoScreen() {
|
||||
private fun BookInfoScreen(snackbarState: SnackbarHostState) {
|
||||
val context = LocalContext.current
|
||||
val state = LocalBookInfoViewModel.current.state
|
||||
val onEvent = LocalBookInfoViewModel.current.onEvent
|
||||
val onNavigate = LocalOnNavigate.current
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val snackbarState = remember { SnackbarHostState() }
|
||||
val refreshState = rememberPullRefreshState(
|
||||
refreshing = state.value.isRefreshing || state.value.isLoadingUpdate,
|
||||
refreshing = state.value.updating || state.value.checkingForUpdate,
|
||||
onRefresh = {
|
||||
onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
BookInfoEvent.OnCheckForUpdate(
|
||||
snackbarState,
|
||||
context
|
||||
)
|
||||
|
|
@ -181,7 +187,7 @@ private fun BookInfoScreen() {
|
|||
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (!state.value.isRefreshing) {
|
||||
if (!state.value.updating) {
|
||||
onEvent(BookInfoEvent.OnNavigateToReaderScreen(onNavigate = onNavigate))
|
||||
}
|
||||
},
|
||||
|
|
@ -221,7 +227,7 @@ private fun BookInfoScreen() {
|
|||
)
|
||||
|
||||
PullRefreshIndicator(
|
||||
state.value.isRefreshing || state.value.isLoadingUpdate,
|
||||
state.value.updating || state.value.checkingForUpdate,
|
||||
refreshState,
|
||||
Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
|
|
@ -254,7 +260,7 @@ private fun BookInfoScreen() {
|
|||
return@BackHandler
|
||||
}
|
||||
|
||||
if (!state.value.isRefreshing) {
|
||||
if (!state.value.updating) {
|
||||
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||
onNavigate {
|
||||
navigateBack()
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ fun BookInfoMoreDropDown(snackbarState: SnackbarHostState) {
|
|||
contentDescription = R.string.show_dropdown_content_desc,
|
||||
disableOnClick = false,
|
||||
enabled = !showDropDown &&
|
||||
!state.value.isRefreshing
|
||||
!state.value.updating
|
||||
) {
|
||||
showDropDown = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ fun BookInfoTopBar(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
GoBackButton(onNavigate = onNavigate, enabled = !state.value.isRefreshing) {
|
||||
GoBackButton(onNavigate = onNavigate, enabled = !state.value.updating) {
|
||||
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||
}
|
||||
}
|
||||
|
|
@ -106,10 +106,10 @@ fun BookInfoTopBar(
|
|||
icon = Icons.Outlined.Refresh,
|
||||
contentDescription = R.string.refresh_book_content_desc,
|
||||
disableOnClick = false,
|
||||
enabled = !state.value.isRefreshing && !state.value.isLoadingUpdate
|
||||
enabled = !state.value.updating && !state.value.checkingForUpdate
|
||||
) {
|
||||
onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
BookInfoEvent.OnCheckForUpdate(
|
||||
snackbarState,
|
||||
context
|
||||
)
|
||||
|
|
@ -135,7 +135,7 @@ fun BookInfoTopBar(
|
|||
icon = Icons.Outlined.Done,
|
||||
contentDescription = R.string.apply_changes_content_desc,
|
||||
disableOnClick = true,
|
||||
enabled = !state.value.isRefreshing &&
|
||||
enabled = !state.value.updating &&
|
||||
(state.value.titleValue.isNotBlank() &&
|
||||
state.value.titleValue.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.filled.Update
|
||||
|
|
@ -101,7 +101,7 @@ sealed class BookInfoEvent {
|
|||
val snackbarState: SnackbarHostState
|
||||
) : BookInfoEvent()
|
||||
|
||||
data class OnLoadUpdate(
|
||||
data class OnCheckForUpdate(
|
||||
val snackbarState: SnackbarHostState,
|
||||
val context: Context
|
||||
) : BookInfoEvent()
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import ua.acclorite.book_story.presentation.core.constants.Constants
|
|||
data class BookInfoState(
|
||||
val book: Book = Constants.EMPTY_BOOK,
|
||||
|
||||
val isLoadingUpdate: Boolean = false,
|
||||
val isRefreshing: Boolean = false,
|
||||
val checkingForUpdate: Boolean = false,
|
||||
val updating: Boolean = false,
|
||||
|
||||
val showConfirmUpdateDialog: Boolean = false,
|
||||
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.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -451,13 +453,13 @@ class BookInfoViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
is BookInfoEvent.OnLoadUpdate -> {
|
||||
is BookInfoEvent.OnCheckForUpdate -> {
|
||||
updateJob?.cancel()
|
||||
updateJob = launch(Dispatchers.IO) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
showConfirmUpdateDialog = false,
|
||||
isLoadingUpdate = true,
|
||||
checkingForUpdate = true,
|
||||
editTitle = false,
|
||||
editAuthor = false,
|
||||
editDescription = false
|
||||
|
|
@ -477,7 +479,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
action = event.context.getString(R.string.retry),
|
||||
onAction = {
|
||||
onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
BookInfoEvent.OnCheckForUpdate(
|
||||
snackbarState = event.snackbarState,
|
||||
context = event.context
|
||||
)
|
||||
|
|
@ -491,7 +493,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoadingUpdate = false
|
||||
checkingForUpdate = false
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
|
|
@ -508,7 +510,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
action = event.context.getString(R.string.retry),
|
||||
onAction = {
|
||||
onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
BookInfoEvent.OnCheckForUpdate(
|
||||
snackbarState = event.snackbarState,
|
||||
context = event.context
|
||||
)
|
||||
|
|
@ -522,7 +524,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoadingUpdate = false
|
||||
checkingForUpdate = false
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
|
|
@ -554,7 +556,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoadingUpdate = false
|
||||
checkingForUpdate = false
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
|
|
@ -571,7 +573,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoadingUpdate = false
|
||||
checkingForUpdate = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -601,7 +603,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
launch(Dispatchers.IO) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = true,
|
||||
updating = true,
|
||||
showConfirmUpdateDialog = false
|
||||
)
|
||||
}
|
||||
|
|
@ -615,7 +617,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
action = event.context.getString(R.string.retry),
|
||||
onAction = {
|
||||
onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
BookInfoEvent.OnCheckForUpdate(
|
||||
snackbarState = event.snackbarState,
|
||||
context = event.context
|
||||
)
|
||||
|
|
@ -629,7 +631,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false,
|
||||
updating = false,
|
||||
updatedText = null,
|
||||
updatedChapters = null
|
||||
)
|
||||
|
|
@ -660,7 +662,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
action = event.context.getString(R.string.retry),
|
||||
onAction = {
|
||||
onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
BookInfoEvent.OnCheckForUpdate(
|
||||
snackbarState = event.snackbarState,
|
||||
context = event.context
|
||||
)
|
||||
|
|
@ -674,7 +676,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false,
|
||||
updating = false,
|
||||
updatedText = null,
|
||||
updatedChapters = null
|
||||
)
|
||||
|
|
@ -692,7 +694,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
action = event.context.getString(R.string.retry),
|
||||
onAction = {
|
||||
onEvent(
|
||||
BookInfoEvent.OnLoadUpdate(
|
||||
BookInfoEvent.OnCheckForUpdate(
|
||||
snackbarState = event.snackbarState,
|
||||
context = event.context
|
||||
)
|
||||
|
|
@ -706,7 +708,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false,
|
||||
updating = false,
|
||||
updatedText = null,
|
||||
updatedChapters = null
|
||||
)
|
||||
|
|
@ -735,7 +737,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
delay(500)
|
||||
_state.update {
|
||||
it.copy(
|
||||
isRefreshing = false
|
||||
updating = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -748,7 +750,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
|
||||
it.copy(
|
||||
showConfirmUpdateDialog = false,
|
||||
isLoadingUpdate = false,
|
||||
checkingForUpdate = false,
|
||||
updatedText = 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) {
|
||||
val book = getBookById.execute(screen.bookId)
|
||||
|
||||
|
|
@ -796,6 +803,22 @@ class BookInfoViewModel @Inject constructor(
|
|||
}
|
||||
clear()
|
||||
|
||||
if (screen.startUpdate) {
|
||||
onEvent(
|
||||
BookInfoEvent.OnCheckForUpdate(
|
||||
snackbarState = snackbarState,
|
||||
context = context
|
||||
)
|
||||
)
|
||||
onNavigate {
|
||||
putScreen(
|
||||
Screen.BookInfo(
|
||||
screen.bookId,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
onEvent(BookInfoEvent.OnCheckCoverReset)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,7 +214,10 @@ private fun HistoryScreen() {
|
|||
onBodyClick = {
|
||||
onNavigate {
|
||||
navigate(
|
||||
Screen.BookInfo(it.bookId)
|
||||
Screen.BookInfo(
|
||||
bookId = it.bookId,
|
||||
startUpdate = false
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -174,7 +174,12 @@ private fun LibraryScreen(
|
|||
onEvent(LibraryEvent.OnSelectBook(it))
|
||||
} else {
|
||||
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.ReaderPerceptionExpander
|
||||
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.ReaderTopBar
|
||||
import ua.acclorite.book_story.presentation.screens.reader.components.readerFastColorPresetChange
|
||||
|
|
@ -117,6 +118,15 @@ fun ReaderScreenRoot(screen: Screen.Reader) {
|
|||
screen = screen,
|
||||
onNavigate = onNavigate,
|
||||
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,
|
||||
refreshList = {
|
||||
onLibraryEvent(LibraryEvent.OnUpdateBook(it))
|
||||
|
|
@ -350,6 +360,9 @@ private fun ReaderScreen(lazyListState: LazyListState) {
|
|||
if (state.value.showSettingsBottomSheet) {
|
||||
ReaderSettingsBottomSheet()
|
||||
}
|
||||
if (state.value.showUpdateDialog) {
|
||||
ReaderUpdateDialog()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
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.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.basicMarquee
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
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.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -18,14 +26,17 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
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.LocalHistoryViewModel
|
||||
import ua.acclorite.book_story.presentation.core.components.LocalLibraryViewModel
|
||||
|
|
@ -92,8 +103,10 @@ fun ReaderTopBar() {
|
|||
fontSize = 20.sp,
|
||||
lineHeight = 20.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.noRippleClickable(
|
||||
enabled = !state.value.lockMenu,
|
||||
onClick = {
|
||||
|
|
@ -107,7 +120,10 @@ fun ReaderTopBar() {
|
|||
navigate = {
|
||||
onNavigate {
|
||||
navigate(
|
||||
Screen.BookInfo(state.value.book.id),
|
||||
Screen.BookInfo(
|
||||
bookId = state.value.book.id,
|
||||
startUpdate = false
|
||||
),
|
||||
useBackAnimation = true,
|
||||
saveInBackStack = false
|
||||
)
|
||||
|
|
@ -117,8 +133,9 @@ fun ReaderTopBar() {
|
|||
)
|
||||
}
|
||||
)
|
||||
.basicMarquee(
|
||||
iterations = Int.MAX_VALUE
|
||||
.then(
|
||||
if (state.value.checkingForUpdate || state.value.updateFound) Modifier
|
||||
else Modifier.basicMarquee(iterations = Int.MAX_VALUE)
|
||||
)
|
||||
)
|
||||
Text(
|
||||
|
|
@ -132,6 +149,40 @@ fun ReaderTopBar() {
|
|||
}
|
||||
},
|
||||
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) {
|
||||
CustomIconButton(
|
||||
icon = Icons.Rounded.Menu,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ sealed class ReaderEvent {
|
|||
data object OnTextIsEmpty : ReaderEvent()
|
||||
|
||||
data class OnLoadText(
|
||||
val checkForUpdate: () -> Unit,
|
||||
val refreshList: (Book) -> Unit,
|
||||
val onError: (UIText) -> Unit,
|
||||
val onTextIsEmpty: () -> Unit
|
||||
|
|
@ -86,4 +87,14 @@ sealed class ReaderEvent {
|
|||
val context: ComponentActivity,
|
||||
val noAppsFound: () -> Unit
|
||||
) : 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 lockMenu: Boolean = false,
|
||||
|
||||
val checkingForUpdate: Boolean = false,
|
||||
val updateFound: Boolean = false,
|
||||
val showUpdateDialog: Boolean = false,
|
||||
|
||||
val showSettingsBottomSheet: Boolean = false,
|
||||
val currentPage: Int = 0,
|
||||
)
|
||||
|
|
@ -22,18 +22,22 @@ import kotlinx.coroutines.flow.debounce
|
|||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.yield
|
||||
import ua.acclorite.book_story.R
|
||||
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.GetLatestHistory
|
||||
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.util.OnNavigate
|
||||
import ua.acclorite.book_story.domain.util.Resource
|
||||
import ua.acclorite.book_story.domain.util.UIText
|
||||
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.launchActivity
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
|
@ -43,7 +47,8 @@ class ReaderViewModel @Inject constructor(
|
|||
private val updateBook: UpdateBook,
|
||||
private val getText: GetText,
|
||||
private val getLatestHistory: GetLatestHistory,
|
||||
private val getBookById: GetBookById
|
||||
private val getBookById: GetBookById,
|
||||
private val parseText: ParseText
|
||||
) : BaseViewModel<ReaderState, ReaderEvent>() {
|
||||
|
||||
private val _state = MutableStateFlow(ReaderState())
|
||||
|
|
@ -51,6 +56,7 @@ class ReaderViewModel @Inject constructor(
|
|||
|
||||
private var eventJob = SupervisorJob()
|
||||
private var scrollJob: Job? = null
|
||||
private var updateJob: Job? = null
|
||||
|
||||
override fun onEvent(event: ReaderEvent) {
|
||||
viewModelScope.launch(eventJob + Dispatchers.Main) {
|
||||
|
|
@ -95,6 +101,8 @@ class ReaderViewModel @Inject constructor(
|
|||
_state.value.book.scrollOffset
|
||||
)
|
||||
|
||||
event.checkForUpdate()
|
||||
|
||||
delay(100)
|
||||
_state.update {
|
||||
it.copy(
|
||||
|
|
@ -399,6 +407,97 @@ class ReaderViewModel @Inject constructor(
|
|||
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,
|
||||
onNavigate: OnNavigate,
|
||||
fullscreenMode: Boolean,
|
||||
checkForTextUpdate: Boolean,
|
||||
checkForTextUpdateToast: () -> Unit,
|
||||
activity: ComponentActivity,
|
||||
refreshList: (Book) -> Unit,
|
||||
onError: (UIText) -> Unit
|
||||
|
|
@ -437,6 +538,15 @@ class ReaderViewModel @Inject constructor(
|
|||
)
|
||||
onEvent(
|
||||
ReaderEvent.OnLoadText(
|
||||
checkForUpdate = {
|
||||
if (checkForTextUpdate) {
|
||||
onEvent(
|
||||
ReaderEvent.OnCheckTextForUpdate {
|
||||
checkForTextUpdateToast()
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
refreshList = { refreshList(it) },
|
||||
onError = {
|
||||
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 ua.acclorite.book_story.R
|
||||
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.HideBarsOnFastScrollSetting
|
||||
import ua.acclorite.book_story.presentation.screens.settings.nested.reader.components.settings.KeepScreenOnSetting
|
||||
|
|
@ -34,6 +36,14 @@ fun LazyListScope.MiscSubcategory(
|
|||
topPadding = topPadding,
|
||||
bottomPadding = bottomPadding
|
||||
) {
|
||||
item {
|
||||
CheckForTextUpdateSetting()
|
||||
}
|
||||
|
||||
item {
|
||||
CheckForTextUpdateToastSetting()
|
||||
}
|
||||
|
||||
item {
|
||||
FullscreenSetting()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
<string name="delete_history">Видалити історію читання?</string>
|
||||
<string name="confirm_update">Підтвердити оновлення?</string>
|
||||
<string name="enable_check_for_updates">Увімнкути перевірку на оновлення?</string>
|
||||
<string name="update_book">Оновити книгу?</string>
|
||||
|
||||
<!-- Dialog Descriptions -->
|
||||
<string name="storage_permission_description">
|
||||
|
|
@ -75,6 +76,13 @@
|
|||
з «api.github.com» кожен раз, коли застосунок запускається.
|
||||
Хочете продовжити та увімкнути «Перевіряти на оновлення»?
|
||||
</string>
|
||||
<string name="update_book_description">
|
||||
Знайдено оновлення тексту книги.
|
||||
Оновлення необхідне для того, щоб забезпечити використання найновіших методів парсингу.
|
||||
Це автономний процес і в більшості випадків пов\'язаний з оновленням парсеру текста.
|
||||
Ваш прогрес не постраждає, якщо не відбудуться значні зміни.
|
||||
Чи бажаєте ви продовжити оновлення?
|
||||
</string>
|
||||
|
||||
<!-- Errors -->
|
||||
<string name="error_permission">
|
||||
|
|
@ -153,6 +161,7 @@
|
|||
<string name="enable">Увімкнути</string>
|
||||
<string name="web_search">Веб-пошук</string>
|
||||
<string name="share">Поділитися</string>
|
||||
<string name="proceed">Продовжити</string>
|
||||
|
||||
<!-- Items -->
|
||||
<string name="files">файли</string>
|
||||
|
|
@ -250,6 +259,9 @@
|
|||
</string>
|
||||
<string name="perception_expander_padding_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 -->
|
||||
<string name="browse_files_structure_option">Структура файлів</string>
|
||||
|
|
@ -340,7 +352,7 @@
|
|||
<string name="history_element_deleted">Елемент історії був успішно видалений.</string>
|
||||
<string name="file_not_found">Файл \"%1$s\" не знайдено.</string>
|
||||
<string name="book_updated">Текст книги був успішно оновлений.</string>
|
||||
<string name="nothing_changed">Текст книги актуальний.</string>
|
||||
<string name="nothing_changed">Текст актуальний.</string>
|
||||
|
||||
<!-- DropDown properties -->
|
||||
<string name="move_this_book">Перемістити</string>
|
||||
|
|
@ -606,5 +618,6 @@
|
|||
<string name="checkpoint_back_content_desc">Чекпоінт назад</string>
|
||||
<string name="checkpoint_forward_content_desc">Чекпоінт вперед</string>
|
||||
<string name="chapters_content_desc">Розділи</string>
|
||||
<string name="update_text_content_desc">Оновити текст</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -32,6 +32,7 @@
|
|||
<string name="delete_history">Delete reading history?</string>
|
||||
<string name="confirm_update">Confirm update?</string>
|
||||
<string name="enable_check_for_updates">Enable check for updates?</string>
|
||||
<string name="update_book">Update book?</string>
|
||||
|
||||
<!-- Dialog Descriptions -->
|
||||
<string name="storage_permission_description">
|
||||
|
|
@ -65,7 +66,7 @@
|
|||
</string>
|
||||
<string name="confirm_update_description">
|
||||
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 name="update_app_description">
|
||||
New version of Book\'s Story was found.
|
||||
|
|
@ -79,6 +80,13 @@
|
|||
when app starts.
|
||||
Do you want to proceed and enable «Check for updates»?
|
||||
</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 -->
|
||||
<string name="error_permission">
|
||||
|
|
@ -156,6 +164,7 @@
|
|||
<string name="enable">Enable</string>
|
||||
<string name="web_search">Web search</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="proceed">Proceed</string>
|
||||
|
||||
<!-- Items -->
|
||||
<string name="files">files</string>
|
||||
|
|
@ -253,6 +262,9 @@
|
|||
</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="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 -->
|
||||
<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="file_not_found">File \"%1$s\" was not found.</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 -->
|
||||
<string name="move_this_book">Move</string>
|
||||
|
|
@ -611,5 +623,6 @@
|
|||
<string name="checkpoint_back_content_desc">Checkpoint back</string>
|
||||
<string name="checkpoint_forward_content_desc">Checkpoint forward</string>
|
||||
<string name="chapters_content_desc">Chapters</string>
|
||||
<string name="update_text_content_desc">Update text</string>
|
||||
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue