🛠️ 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 androidx.compose.ui.focus.FocusRequester
import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category 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.OnNavigate
import ua.acclorite.book_story.domain.util.UIText import ua.acclorite.book_story.domain.util.UIText
@Immutable @Immutable
sealed class BookInfoEvent { sealed class BookInfoEvent {
data object OnShowHideChangeCoverBottomSheet : BookInfoEvent() data object OnShowHideChangeCoverBottomSheet : BookInfoEvent()
data object OnShowHideDetailsBottomSheet : 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) : data class OnDeleteCover(
BookInfoEvent() val refreshList: (Book) -> Unit
) : BookInfoEvent()
data class OnChangeCover(
val uri: Uri,
val context: Context,
val refreshList: (Book) -> Unit
) : BookInfoEvent()
data object OnCheckCoverReset : BookInfoEvent() data object OnCheckCoverReset : BookInfoEvent()
data class OnResetCoverImage( data class OnResetCoverImage(
val refreshList: (Book) -> Unit, val refreshList: (Book) -> Unit,
val showResult: (UIText) -> Unit val showResult: (UIText) -> Unit
) : BookInfoEvent() ) : BookInfoEvent()
data object OnShowHideEditTitle : 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 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 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 OnShowHideDeleteDialog : BookInfoEvent()
data object OnShowHideMoveDialog : 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( data class OnMoveBook(
val refreshList: (Book) -> Unit, val refreshList: (Book) -> Unit,
@ -49,7 +83,10 @@ sealed class BookInfoEvent {
val onNavigate: OnNavigate val onNavigate: OnNavigate
) : BookInfoEvent() ) : BookInfoEvent()
data class OnSelectCategory(val category: Category) : BookInfoEvent() data class OnSelectCategory(
val category: Category
) : BookInfoEvent()
data class OnCopyToClipboard( data class OnCopyToClipboard(
val context: Context, val context: Context,
val text: String, val text: String,
@ -76,7 +113,8 @@ sealed class BookInfoEvent {
data object OnDismissConfirmUpdateDialog : BookInfoEvent() data object OnDismissConfirmUpdateDialog : BookInfoEvent()
data class OnShowConfirmUpdateDialog( data class OnShowConfirmUpdateDialog(
val updatedText: List<String> val updatedText: List<String>,
val updatedChapters: List<Chapter>
) : BookInfoEvent() ) : BookInfoEvent()
data object OnCancelUpdate : 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 androidx.compose.runtime.Immutable
import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category 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 import ua.acclorite.book_story.presentation.core.constants.Constants
@Immutable @Immutable
@ -11,8 +12,10 @@ data class BookInfoState(
val isLoadingUpdate: Boolean = false, val isLoadingUpdate: Boolean = false,
val isRefreshing: Boolean = false, val isRefreshing: Boolean = false,
val showConfirmUpdateDialog: Boolean = false, val showConfirmUpdateDialog: Boolean = false,
val updatedText: List<String>? = null, val updatedText: List<String>? = null,
val updatedChapters: List<Chapter>? = null,
val editTitle: Boolean = false, val editTitle: Boolean = false,
val hasTitleFocused: 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.BookWithText
import ua.acclorite.book_story.domain.model.Category import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.model.History 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.CanResetCover
import ua.acclorite.book_story.domain.use_case.DeleteBooks 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.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.GetText
import ua.acclorite.book_story.domain.use_case.InsertHistory 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.ResetCoverImage
import ua.acclorite.book_story.domain.use_case.UpdateBook 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.UpdateBookWithText
import ua.acclorite.book_story.domain.use_case.UpdateCoverImageOfBook import ua.acclorite.book_story.domain.use_case.UpdateCoverImageOfBook
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 java.io.File import java.io.File
import java.util.Date import java.util.Date
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.roundToInt
@HiltViewModel @HiltViewModel
@ -50,11 +51,11 @@ class BookInfoViewModel @Inject constructor(
private val updateCoverImageOfBook: UpdateCoverImageOfBook, private val updateCoverImageOfBook: UpdateCoverImageOfBook,
private val insertHistory: InsertHistory, private val insertHistory: InsertHistory,
private val deleteBooks: DeleteBooks, private val deleteBooks: DeleteBooks,
private val getBookFromFile: GetBookFromFile,
private val getBookById: GetBookById, private val getBookById: GetBookById,
private val getText: GetText, private val getText: GetText,
private val canResetCover: CanResetCover, private val canResetCover: CanResetCover,
private val resetCoverImage: ResetCoverImage, private val resetCoverImage: ResetCoverImage,
private val parseText: ParseText
) : BaseViewModel<BookInfoState, BookInfoEvent>() { ) : BaseViewModel<BookInfoState, BookInfoEvent>() {
private val _state = MutableStateFlow(BookInfoState()) private val _state = MutableStateFlow(BookInfoState())
@ -464,83 +465,84 @@ class BookInfoViewModel @Inject constructor(
} }
yield() yield()
if (!File(_state.value.book.filePath).exists()) {
onEvent( val currentBook = _state.value.book.apply {
BookInfoEvent.OnShowSnackbar( if (!File(filePath).exists()) {
text = event.context.getString( onEvent(
R.string.file_not_found, BookInfoEvent.OnShowSnackbar(
_state.value.book.filePath text = event.context.getString(
.substringAfterLast("/") R.string.file_not_found,
.takeLast(25) filePath.substringAfterLast("/").takeLast(25)
), ),
action = event.context.getString(R.string.retry), action = event.context.getString(R.string.retry),
onAction = { onAction = {
onEvent( onEvent(
BookInfoEvent.OnLoadUpdate( BookInfoEvent.OnLoadUpdate(
snackbarState = event.snackbarState, snackbarState = event.snackbarState,
context = event.context context = event.context
)
) )
) },
}, durationMillis = 4000L,
durationMillis = 4000L, snackbarState = event.snackbarState
snackbarState = event.snackbarState )
)
)
delay(500)
_state.update {
it.copy(
isLoadingUpdate = false
) )
delay(500)
_state.update {
it.copy(
isLoadingUpdate = false
)
}
return@launch
} }
return@launch
} }
val updatedBook = parseText.execute(File(currentBook.filePath)).apply {
yield() if (this is Resource.Error || data == null) {
val updatedBook = getBookFromFile.execute(File(_state.value.book.filePath)) onEvent(
yield() BookInfoEvent.OnShowSnackbar(
text = message?.asString(event.context)
if (updatedBook is NullableBook.Null) { ?: event.context.getString(
onEvent( R.string.error_something_went_wrong_with_file
BookInfoEvent.OnShowSnackbar( ),
text = updatedBook.message?.asString(event.context) action = event.context.getString(R.string.retry),
?: event.context.getString(R.string.error_something_went_wrong_with_file), onAction = {
action = event.context.getString(R.string.retry), onEvent(
onAction = { BookInfoEvent.OnLoadUpdate(
onEvent( snackbarState = event.snackbarState,
BookInfoEvent.OnLoadUpdate( context = event.context
snackbarState = event.snackbarState, )
context = event.context
) )
) },
}, durationMillis = 4000L,
durationMillis = 4000L, snackbarState = event.snackbarState
snackbarState = event.snackbarState )
) )
)
delay(500) delay(500)
_state.update { _state.update {
it.copy( it.copy(
isLoadingUpdate = false isLoadingUpdate = false
) )
}
return@launch
} }
return@launch }.data!!
}
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
}
yield() 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( onEvent(
BookInfoEvent.OnShowSnackbar( BookInfoEvent.OnShowSnackbar(
event.context.getString(R.string.nothing_changed), event.context.getString(R.string.nothing_changed),
@ -559,9 +561,11 @@ class BookInfoViewModel @Inject constructor(
} }
yield() yield()
onEvent( onEvent(
BookInfoEvent.OnShowConfirmUpdateDialog( BookInfoEvent.OnShowConfirmUpdateDialog(
updatedText = updatedBook.bookWithTextAndCover.text, updatedText = updatedText,
updatedChapters = updatedChapters
) )
) )
@ -577,7 +581,8 @@ class BookInfoViewModel @Inject constructor(
_state.update { _state.update {
it.copy( it.copy(
showConfirmUpdateDialog = false, showConfirmUpdateDialog = false,
updatedText = null updatedText = null,
updatedChapters = null
) )
} }
} }
@ -586,7 +591,8 @@ class BookInfoViewModel @Inject constructor(
_state.update { _state.update {
it.copy( it.copy(
showConfirmUpdateDialog = true, 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( onEvent(
BookInfoEvent.OnShowSnackbar( BookInfoEvent.OnShowSnackbar(
text = event.context.getString( text = event.context.getString(
@ -619,24 +625,33 @@ class BookInfoViewModel @Inject constructor(
snackbarState = event.snackbarState snackbarState = event.snackbarState
) )
) )
delay(500) delay(500)
_state.update { _state.update {
it.copy( it.copy(
isRefreshing = false isRefreshing = false,
updatedText = null,
updatedChapters = null
) )
} }
return@launch return@launch
} }
val updatedText = _state.value.updatedText ?: return@launch val updatedText = _state.value.updatedText ?: return@launch
val isSuccess = updateBookWithText.execute( val updatedChapters = _state.value.updatedChapters ?: return@launch
updateBookWithText.execute(
BookWithText( BookWithText(
book = _state.value.book, book = _state.value.book.copy(
scrollIndex = (updatedText.size * _state.value.book.progress).roundToInt(),
scrollOffset = 0,
chapters = updatedChapters
),
text = updatedText text = updatedText
) )
) ).apply {
if (this) return@apply
if (!isSuccess) {
onEvent( onEvent(
BookInfoEvent.OnShowSnackbar( BookInfoEvent.OnShowSnackbar(
text = event.context.getString( text = event.context.getString(
@ -655,66 +670,74 @@ class BookInfoViewModel @Inject constructor(
snackbarState = event.snackbarState snackbarState = event.snackbarState
) )
) )
delay(500) delay(500)
_state.update { _state.update {
it.copy( it.copy(
isRefreshing = false isRefreshing = false,
updatedText = null,
updatedChapters = null
) )
} }
return@launch return@launch
} }
val newBook = getBookById.execute(_state.value.book.id) getBookById.execute(_state.value.book.id).apply {
if (newBook == null) { 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( onEvent(
BookInfoEvent.OnShowSnackbar( BookInfoEvent.OnShowSnackbar(
text = event.context.getString( event.context.getString(R.string.book_updated),
R.string.error_something_went_wrong_with_file action = null,
),
action = event.context.getString(R.string.retry),
onAction = {
onEvent(
BookInfoEvent.OnLoadUpdate(
snackbarState = event.snackbarState,
context = event.context
)
)
},
durationMillis = 4000L, durationMillis = 4000L,
snackbarState = event.snackbarState snackbarState = event.snackbarState
) )
) )
delay(500) delay(500)
_state.update { _state.update {
it.copy( it.copy(
isRefreshing = false 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( it.copy(
showConfirmUpdateDialog = false, showConfirmUpdateDialog = false,
isLoadingUpdate = false isLoadingUpdate = false,
updatedText = null,
updatedChapters = null
) )
} }
} }

View file

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

View file

@ -64,8 +64,8 @@
After deleting the whole history, the order of the books will be reset. After deleting the whole history, the order of the books will be reset.
</string> </string>
<string name="confirm_update_description"> <string name="confirm_update_description">
By confirming this book\'s text will be updated. Update this book\'s text and chapters.
Please remember where you left before updating text. The progress should remain unchanged unless 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.