🛠️ Update book's update in BookInfo

* Updated book's text update to match latest app behaviour(chapters)
This commit is contained in:
Acclorite 2024-09-20 18:11:18 +03:00
parent 2334e9c3ec
commit 5eaa3963a7
5 changed files with 204 additions and 138 deletions

View file

@ -7,41 +7,75 @@ import androidx.compose.runtime.Immutable
import androidx.compose.ui.focus.FocusRequester
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.model.Chapter
import ua.acclorite.book_story.domain.util.OnNavigate
import ua.acclorite.book_story.domain.util.UIText
@Immutable
sealed class BookInfoEvent {
data object OnShowHideChangeCoverBottomSheet : BookInfoEvent()
data object OnShowHideDetailsBottomSheet : BookInfoEvent()
data class OnDeleteCover(val refreshList: (Book) -> Unit) : BookInfoEvent()
data class OnChangeCover(val uri: Uri, val context: Context, val refreshList: (Book) -> Unit) :
BookInfoEvent()
data class OnDeleteCover(
val refreshList: (Book) -> Unit
) : BookInfoEvent()
data class OnChangeCover(
val uri: Uri,
val context: Context,
val refreshList: (Book) -> Unit
) : BookInfoEvent()
data object OnCheckCoverReset : BookInfoEvent()
data class OnResetCoverImage(
val refreshList: (Book) -> Unit,
val showResult: (UIText) -> Unit
) : BookInfoEvent()
data object OnShowHideEditTitle : BookInfoEvent()
data class OnTitleRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent()
data class OnTitleValueChange(val value: String) : BookInfoEvent()
data class OnTitleRequestFocus(
val focusRequester: FocusRequester
) : BookInfoEvent()
data class OnTitleValueChange(
val value: String
) : BookInfoEvent()
data object OnShowHideEditAuthor : BookInfoEvent()
data class OnAuthorRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent()
data class OnAuthorValueChange(val value: String) : BookInfoEvent()
data class OnAuthorRequestFocus(
val focusRequester: FocusRequester
) : BookInfoEvent()
data class OnAuthorValueChange(
val value: String
) : BookInfoEvent()
data object OnShowHideEditDescription : BookInfoEvent()
data class OnDescriptionRequestFocus(val focusRequester: FocusRequester) : BookInfoEvent()
data class OnDescriptionValueChange(val value: String) : BookInfoEvent()
data class OnUpdateData(val refreshList: (Book) -> Unit) : BookInfoEvent()
data class OnDescriptionRequestFocus(
val focusRequester: FocusRequester
) : BookInfoEvent()
data class OnDescriptionValueChange(
val value: String
) : BookInfoEvent()
data class OnUpdateData(
val refreshList: (Book) -> Unit
) : BookInfoEvent()
data object OnShowHideDeleteDialog : BookInfoEvent()
data object OnShowHideMoveDialog : BookInfoEvent()
data class OnDeleteBook(val refreshList: () -> Unit, val onNavigate: OnNavigate) :
BookInfoEvent()
data class OnDeleteBook(
val refreshList: () -> Unit,
val onNavigate: OnNavigate
) : BookInfoEvent()
data class OnMoveBook(
val refreshList: (Book) -> Unit,
@ -49,7 +83,10 @@ sealed class BookInfoEvent {
val onNavigate: OnNavigate
) : BookInfoEvent()
data class OnSelectCategory(val category: Category) : BookInfoEvent()
data class OnSelectCategory(
val category: Category
) : BookInfoEvent()
data class OnCopyToClipboard(
val context: Context,
val text: String,
@ -76,7 +113,8 @@ sealed class BookInfoEvent {
data object OnDismissConfirmUpdateDialog : BookInfoEvent()
data class OnShowConfirmUpdateDialog(
val updatedText: List<String>
val updatedText: List<String>,
val updatedChapters: List<Chapter>
) : BookInfoEvent()
data object OnCancelUpdate : BookInfoEvent()

View file

@ -3,6 +3,7 @@ package ua.acclorite.book_story.presentation.screens.book_info.data
import androidx.compose.runtime.Immutable
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.model.Chapter
import ua.acclorite.book_story.presentation.core.constants.Constants
@Immutable
@ -11,8 +12,10 @@ data class BookInfoState(
val isLoadingUpdate: Boolean = false,
val isRefreshing: Boolean = false,
val showConfirmUpdateDialog: Boolean = false,
val updatedText: List<String>? = null,
val updatedChapters: List<Chapter>? = null,
val editTitle: Boolean = false,
val hasTitleFocused: Boolean = false,

View file

@ -23,24 +23,25 @@ import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.BookWithText
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.model.NullableBook
import ua.acclorite.book_story.domain.use_case.CanResetCover
import ua.acclorite.book_story.domain.use_case.DeleteBooks
import ua.acclorite.book_story.domain.use_case.GetBookById
import ua.acclorite.book_story.domain.use_case.GetBookFromFile
import ua.acclorite.book_story.domain.use_case.GetText
import ua.acclorite.book_story.domain.use_case.InsertHistory
import ua.acclorite.book_story.domain.use_case.ParseText
import ua.acclorite.book_story.domain.use_case.ResetCoverImage
import ua.acclorite.book_story.domain.use_case.UpdateBook
import ua.acclorite.book_story.domain.use_case.UpdateBookWithText
import ua.acclorite.book_story.domain.use_case.UpdateCoverImageOfBook
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 java.io.File
import java.util.Date
import javax.inject.Inject
import kotlin.math.roundToInt
@HiltViewModel
@ -50,11 +51,11 @@ class BookInfoViewModel @Inject constructor(
private val updateCoverImageOfBook: UpdateCoverImageOfBook,
private val insertHistory: InsertHistory,
private val deleteBooks: DeleteBooks,
private val getBookFromFile: GetBookFromFile,
private val getBookById: GetBookById,
private val getText: GetText,
private val canResetCover: CanResetCover,
private val resetCoverImage: ResetCoverImage,
private val parseText: ParseText
) : BaseViewModel<BookInfoState, BookInfoEvent>() {
private val _state = MutableStateFlow(BookInfoState())
@ -464,83 +465,84 @@ class BookInfoViewModel @Inject constructor(
}
yield()
if (!File(_state.value.book.filePath).exists()) {
onEvent(
BookInfoEvent.OnShowSnackbar(
text = event.context.getString(
R.string.file_not_found,
_state.value.book.filePath
.substringAfterLast("/")
.takeLast(25)
),
action = event.context.getString(R.string.retry),
onAction = {
onEvent(
BookInfoEvent.OnLoadUpdate(
snackbarState = event.snackbarState,
context = event.context
val currentBook = _state.value.book.apply {
if (!File(filePath).exists()) {
onEvent(
BookInfoEvent.OnShowSnackbar(
text = event.context.getString(
R.string.file_not_found,
filePath.substringAfterLast("/").takeLast(25)
),
action = event.context.getString(R.string.retry),
onAction = {
onEvent(
BookInfoEvent.OnLoadUpdate(
snackbarState = event.snackbarState,
context = event.context
)
)
)
},
durationMillis = 4000L,
snackbarState = event.snackbarState
)
)
delay(500)
_state.update {
it.copy(
isLoadingUpdate = false
},
durationMillis = 4000L,
snackbarState = event.snackbarState
)
)
delay(500)
_state.update {
it.copy(
isLoadingUpdate = false
)
}
return@launch
}
return@launch
}
yield()
val updatedBook = getBookFromFile.execute(File(_state.value.book.filePath))
yield()
if (updatedBook is NullableBook.Null) {
onEvent(
BookInfoEvent.OnShowSnackbar(
text = updatedBook.message?.asString(event.context)
?: event.context.getString(R.string.error_something_went_wrong_with_file),
action = event.context.getString(R.string.retry),
onAction = {
onEvent(
BookInfoEvent.OnLoadUpdate(
snackbarState = event.snackbarState,
context = event.context
val updatedBook = parseText.execute(File(currentBook.filePath)).apply {
if (this is Resource.Error || data == null) {
onEvent(
BookInfoEvent.OnShowSnackbar(
text = message?.asString(event.context)
?: event.context.getString(
R.string.error_something_went_wrong_with_file
),
action = event.context.getString(R.string.retry),
onAction = {
onEvent(
BookInfoEvent.OnLoadUpdate(
snackbarState = event.snackbarState,
context = event.context
)
)
)
},
durationMillis = 4000L,
snackbarState = event.snackbarState
},
durationMillis = 4000L,
snackbarState = event.snackbarState
)
)
)
delay(500)
_state.update {
it.copy(
isLoadingUpdate = false
)
delay(500)
_state.update {
it.copy(
isLoadingUpdate = false
)
}
return@launch
}
return@launch
}
yield()
val book = _state.value.book
var textUpdated = false
val updatedText = updatedBook.bookWithTextAndCover!!.text
val text = getText.execute(book.textPath)
if (updatedText != text) {
textUpdated = true
}
}.data!!
yield()
if (!textUpdated) {
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) {
onEvent(
BookInfoEvent.OnShowSnackbar(
event.context.getString(R.string.nothing_changed),
@ -559,9 +561,11 @@ class BookInfoViewModel @Inject constructor(
}
yield()
onEvent(
BookInfoEvent.OnShowConfirmUpdateDialog(
updatedText = updatedBook.bookWithTextAndCover.text,
updatedText = updatedText,
updatedChapters = updatedChapters
)
)
@ -577,7 +581,8 @@ class BookInfoViewModel @Inject constructor(
_state.update {
it.copy(
showConfirmUpdateDialog = false,
updatedText = null
updatedText = null,
updatedChapters = null
)
}
}
@ -586,7 +591,8 @@ class BookInfoViewModel @Inject constructor(
_state.update {
it.copy(
showConfirmUpdateDialog = true,
updatedText = event.updatedText
updatedText = event.updatedText,
updatedChapters = event.updatedChapters
)
}
}
@ -600,7 +606,7 @@ class BookInfoViewModel @Inject constructor(
)
}
if (_state.value.updatedText == null) {
if (_state.value.updatedText == null || _state.value.updatedChapters == null) {
onEvent(
BookInfoEvent.OnShowSnackbar(
text = event.context.getString(
@ -619,24 +625,33 @@ class BookInfoViewModel @Inject constructor(
snackbarState = event.snackbarState
)
)
delay(500)
_state.update {
it.copy(
isRefreshing = false
isRefreshing = false,
updatedText = null,
updatedChapters = null
)
}
return@launch
}
val updatedText = _state.value.updatedText ?: return@launch
val isSuccess = updateBookWithText.execute(
val updatedChapters = _state.value.updatedChapters ?: return@launch
updateBookWithText.execute(
BookWithText(
book = _state.value.book,
book = _state.value.book.copy(
scrollIndex = (updatedText.size * _state.value.book.progress).roundToInt(),
scrollOffset = 0,
chapters = updatedChapters
),
text = updatedText
)
)
).apply {
if (this) return@apply
if (!isSuccess) {
onEvent(
BookInfoEvent.OnShowSnackbar(
text = event.context.getString(
@ -655,66 +670,74 @@ class BookInfoViewModel @Inject constructor(
snackbarState = event.snackbarState
)
)
delay(500)
_state.update {
it.copy(
isRefreshing = false
isRefreshing = false,
updatedText = null,
updatedChapters = null
)
}
return@launch
}
val newBook = getBookById.execute(_state.value.book.id)
if (newBook == null) {
getBookById.execute(_state.value.book.id).apply {
if (this == null) {
onEvent(
BookInfoEvent.OnShowSnackbar(
text = event.context.getString(
R.string.error_something_went_wrong_with_file
),
action = event.context.getString(R.string.retry),
onAction = {
onEvent(
BookInfoEvent.OnLoadUpdate(
snackbarState = event.snackbarState,
context = event.context
)
)
},
durationMillis = 4000L,
snackbarState = event.snackbarState
)
)
delay(500)
_state.update {
it.copy(
isRefreshing = false,
updatedText = null,
updatedChapters = null
)
}
return@launch
}
_state.update {
it.copy(
book = this,
updatedText = null,
updatedChapters = null
)
}
event.refreshList(_state.value.book)
onEvent(
BookInfoEvent.OnShowSnackbar(
text = event.context.getString(
R.string.error_something_went_wrong_with_file
),
action = event.context.getString(R.string.retry),
onAction = {
onEvent(
BookInfoEvent.OnLoadUpdate(
snackbarState = event.snackbarState,
context = event.context
)
)
},
event.context.getString(R.string.book_updated),
action = null,
durationMillis = 4000L,
snackbarState = event.snackbarState
)
)
delay(500)
_state.update {
it.copy(
isRefreshing = false
)
}
return@launch
}
_state.update {
it.copy(
book = newBook,
updatedText = null
)
}
event.refreshList(_state.value.book)
onEvent(
BookInfoEvent.OnShowSnackbar(
event.context.getString(R.string.book_updated),
action = null,
durationMillis = 4000L,
snackbarState = event.snackbarState
)
)
delay(500)
_state.update {
it.copy(
isRefreshing = false
)
}
}
}
@ -725,7 +748,9 @@ class BookInfoViewModel @Inject constructor(
it.copy(
showConfirmUpdateDialog = false,
isLoadingUpdate = false
isLoadingUpdate = false,
updatedText = null,
updatedChapters = null
)
}
}

View file

@ -61,8 +61,8 @@
Після видалення всієї історії порядок книг буде скинутий.
</string>
<string name="confirm_update_description">
При підтвердженні текст цієї книги буде оновлений.
Будь ласка, запам\'ятайте де ви зупинилися перед оновленням тексту.
Оновіть текст та розділи цієї книги.
Прогрес повинен залишитися незмінним, якщо порядок параграфів не був змінений.
</string>
<string name="update_app_description">
Нова версія Book\'s Story була знайдена.

View file

@ -64,8 +64,8 @@
After deleting the whole history, the order of the books will be reset.
</string>
<string name="confirm_update_description">
By confirming this book\'s text will be updated.
Please remember where you left before updating text.
Update this book\'s text and chapters.
The progress should remain unchanged unless order of paragraphs was changed.
</string>
<string name="update_app_description">
New version of Book\'s Story was found.