Changed Reader text system. Now book doesn't hold any text, only ReaderViewModel does. (v1.0.1)
This commit is contained in:
parent
e0698d2c2f
commit
456d9fc3c9
25 changed files with 257 additions and 279 deletions
|
|
@ -25,6 +25,9 @@ android {
|
|||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import ua.acclorite.book_story.R
|
|||
import ua.acclorite.book_story.data.local.dto.BookEntity
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
import ua.acclorite.book_story.domain.util.UIText
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
class BookMapperImpl @Inject constructor() : BookMapper {
|
||||
|
|
@ -26,8 +25,6 @@ class BookMapperImpl @Inject constructor() : BookMapper {
|
|||
}
|
||||
|
||||
override suspend fun toBook(bookEntity: BookEntity): Book {
|
||||
val file = File(bookEntity.filePath)
|
||||
|
||||
return Book(
|
||||
id = bookEntity.id,
|
||||
title = bookEntity.title,
|
||||
|
|
@ -38,11 +35,7 @@ class BookMapperImpl @Inject constructor() : BookMapper {
|
|||
scrollIndex = bookEntity.scrollIndex,
|
||||
scrollOffset = bookEntity.scrollOffset,
|
||||
progress = bookEntity.progress,
|
||||
file = if (file.exists()) file else null,
|
||||
textPath = bookEntity.textPath,
|
||||
text = emptyList(),
|
||||
letters = 0,
|
||||
words = 0,
|
||||
filePath = bookEntity.filePath,
|
||||
lastOpened = null,
|
||||
category = bookEntity.category,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import javax.inject.Inject
|
|||
class EpubFileParser @Inject constructor() : FileParser {
|
||||
|
||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||
if (!file.name.endsWith(".epub")) {
|
||||
if (!file.name.endsWith(".epub") || !file.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -49,13 +49,9 @@ class EpubFileParser @Inject constructor() : FileParser {
|
|||
author = author,
|
||||
description = description?.toString(),
|
||||
textPath = "",
|
||||
text = emptyList(),
|
||||
letters = 0,
|
||||
words = 0,
|
||||
scrollIndex = 0,
|
||||
scrollOffset = 0,
|
||||
progress = 0f,
|
||||
file = file,
|
||||
filePath = file.path,
|
||||
lastOpened = null,
|
||||
category = Category.entries[0],
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import javax.inject.Inject
|
|||
class PdfFileParser @Inject constructor(private val application: Application) : FileParser {
|
||||
|
||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||
if (!file.name.endsWith(".pdf")) {
|
||||
if (!file.name.endsWith(".pdf") || !file.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -37,13 +37,9 @@ class PdfFileParser @Inject constructor(private val application: Application) :
|
|||
author = author,
|
||||
description = description,
|
||||
textPath = "",
|
||||
text = emptyList(),
|
||||
letters = 0,
|
||||
words = 0,
|
||||
scrollIndex = 0,
|
||||
scrollOffset = 0,
|
||||
progress = 0f,
|
||||
file = file,
|
||||
filePath = file.path,
|
||||
lastOpened = null,
|
||||
category = Category.entries[0],
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import javax.inject.Inject
|
|||
class TxtFileParser @Inject constructor() : FileParser {
|
||||
|
||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
||||
if (!file.name.endsWith(".txt")) {
|
||||
if (!file.name.endsWith(".txt") || !file.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -25,13 +25,9 @@ class TxtFileParser @Inject constructor() : FileParser {
|
|||
author = author,
|
||||
description = null,
|
||||
textPath = "",
|
||||
text = emptyList(),
|
||||
letters = 0,
|
||||
words = 0,
|
||||
scrollIndex = 0,
|
||||
scrollOffset = 0,
|
||||
progress = 0f,
|
||||
file = file,
|
||||
filePath = file.path,
|
||||
lastOpened = null,
|
||||
category = Category.entries[0],
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class BookRepositoryImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getBookTextById(textPath: String): List<StringWithId> {
|
||||
override suspend fun getBookText(textPath: String): List<StringWithId> {
|
||||
val textFile = File(textPath)
|
||||
|
||||
if (textPath.isBlank() || !textFile.exists()) {
|
||||
|
|
@ -105,8 +105,10 @@ class BookRepositoryImpl @Inject constructor(
|
|||
return text.data
|
||||
}
|
||||
|
||||
override suspend fun insertBooks(
|
||||
books: List<Pair<Book, CoverImage?>>
|
||||
override suspend fun insertBook(
|
||||
book: Book,
|
||||
coverImage: CoverImage?,
|
||||
text: List<StringWithId>
|
||||
): Boolean {
|
||||
val filesDir = application.filesDir
|
||||
val coversDir = File(filesDir, "covers")
|
||||
|
|
@ -119,21 +121,20 @@ class BookRepositoryImpl @Inject constructor(
|
|||
booksDir.mkdirs()
|
||||
}
|
||||
|
||||
val booksWithCoverAndText = books.map {
|
||||
var coverUri = ""
|
||||
val textUri: String
|
||||
|
||||
if (it.first.text.isEmpty()) {
|
||||
if (text.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
textUri = "${UUID.randomUUID()}.txt"
|
||||
val text = File(booksDir, textUri)
|
||||
val textPath = File(booksDir, textUri)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
FileOutputStream(text).use { stream ->
|
||||
it.first.text.forEach { line ->
|
||||
FileOutputStream(textPath).use { stream ->
|
||||
text.forEach { line ->
|
||||
stream.write(line.line.toByteArray())
|
||||
stream.write(System.lineSeparator().toByteArray())
|
||||
}
|
||||
|
|
@ -144,7 +145,7 @@ class BookRepositoryImpl @Inject constructor(
|
|||
return false
|
||||
}
|
||||
|
||||
if (it.second != null) {
|
||||
if (coverImage != null) {
|
||||
try {
|
||||
coverUri = "${UUID.randomUUID()}.webp"
|
||||
val cover = File(coversDir, coverUri)
|
||||
|
|
@ -152,7 +153,7 @@ class BookRepositoryImpl @Inject constructor(
|
|||
withContext(Dispatchers.IO) {
|
||||
FileOutputStream(cover).use { stream ->
|
||||
if (
|
||||
!it.second!!.copy(Bitmap.Config.RGB_565, false).compress(
|
||||
!coverImage.copy(Bitmap.Config.RGB_565, false).compress(
|
||||
Bitmap.CompressFormat.WEBP,
|
||||
20,
|
||||
stream
|
||||
|
|
@ -169,17 +170,15 @@ class BookRepositoryImpl @Inject constructor(
|
|||
}
|
||||
|
||||
|
||||
val book = it.first.copy(
|
||||
val updatedBook = book.copy(
|
||||
textPath = "$booksDir/$textUri",
|
||||
coverImage = if (coverUri.isNotBlank()) {
|
||||
Uri.fromFile(File("$coversDir/$coverUri"))
|
||||
} else null
|
||||
)
|
||||
|
||||
bookMapper.toBookEntity(book)
|
||||
}
|
||||
|
||||
database.insertBooks(booksWithCoverAndText)
|
||||
val bookToInsert = bookMapper.toBookEntity(updatedBook)
|
||||
database.insertBooks(listOf(bookToInsert))
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +197,7 @@ class BookRepositoryImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun updateBooksWithText(books: List<Book>): Boolean {
|
||||
override suspend fun updateBookWithText(book: Book, text: List<StringWithId>): Boolean {
|
||||
// without cover image
|
||||
val filesDir = application.filesDir
|
||||
val booksDir = File(filesDir, "books")
|
||||
|
|
@ -207,21 +206,20 @@ class BookRepositoryImpl @Inject constructor(
|
|||
booksDir.mkdirs()
|
||||
}
|
||||
|
||||
val booksWithText = books.map {
|
||||
val textUri: String
|
||||
val bookEntity = database.findBookById(it.id)
|
||||
val bookEntity = database.findBookById(book.id)
|
||||
|
||||
if (it.text.isEmpty()) {
|
||||
if (text.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
textUri = "${UUID.randomUUID()}.txt"
|
||||
val text = File(booksDir, textUri)
|
||||
val textPath = File(booksDir, textUri)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
FileOutputStream(text).use { stream ->
|
||||
it.text.forEach { line ->
|
||||
FileOutputStream(textPath).use { stream ->
|
||||
text.forEach { line ->
|
||||
stream.write(line.line.toByteArray())
|
||||
stream.write(System.lineSeparator().toByteArray())
|
||||
}
|
||||
|
|
@ -232,10 +230,10 @@ class BookRepositoryImpl @Inject constructor(
|
|||
return false
|
||||
}
|
||||
|
||||
if (it.textPath.isNotBlank()) {
|
||||
if (book.textPath.isNotBlank()) {
|
||||
try {
|
||||
val fileToDelete = File(
|
||||
it.textPath
|
||||
book.textPath
|
||||
)
|
||||
|
||||
if (fileToDelete.exists()) {
|
||||
|
|
@ -246,16 +244,15 @@ class BookRepositoryImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
bookMapper.toBookEntity(
|
||||
it.copy(
|
||||
val updatedBook = bookMapper.toBookEntity(
|
||||
book.copy(
|
||||
textPath = "$booksDir/$textUri",
|
||||
coverImage = if (bookEntity.image != null) Uri.parse(bookEntity.image) else null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
database.updateBooks(
|
||||
booksWithText
|
||||
listOf(updatedBook)
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
|
@ -560,10 +557,9 @@ class BookRepositoryImpl @Inject constructor(
|
|||
|
||||
books.add(
|
||||
NullableBook.NotNull(
|
||||
Pair(
|
||||
parsedBook.first.copy(text = parsedText.data),
|
||||
parsedBook.second
|
||||
)
|
||||
book = parsedBook.first,
|
||||
coverImage = parsedBook.second,
|
||||
text = parsedText.data
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -613,7 +609,7 @@ class BookRepositoryImpl @Inject constructor(
|
|||
try {
|
||||
val result = githubAPI.getLatestRelease()
|
||||
|
||||
val version = result.tagName.substringAfterLast("v")
|
||||
val version = result.tagName.substringAfter("v")
|
||||
val currentVersion = application.getString(R.string.app_version)
|
||||
|
||||
if (version != currentVersion && postNotification) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package ua.acclorite.book_story.domain.model
|
|||
import android.net.Uri
|
||||
import androidx.compose.runtime.Immutable
|
||||
import ua.acclorite.book_story.domain.util.UIText
|
||||
import java.io.File
|
||||
|
||||
@Immutable
|
||||
data class Book(
|
||||
|
|
@ -14,17 +13,13 @@ data class Book(
|
|||
val description: String?,
|
||||
|
||||
val textPath: String,
|
||||
val text: List<StringWithId> = emptyList(),
|
||||
val letters: Int,
|
||||
val words: Int,
|
||||
val filePath: String,
|
||||
val coverImage: Uri?,
|
||||
|
||||
val scrollIndex: Int,
|
||||
val scrollOffset: Int,
|
||||
val progress: Float,
|
||||
|
||||
val file: File?,
|
||||
val filePath: String,
|
||||
val lastOpened: Long?,
|
||||
val category: Category,
|
||||
val coverImage: Uri?
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,13 +6,26 @@ import ua.acclorite.book_story.domain.util.UIText
|
|||
|
||||
@Immutable
|
||||
sealed class NullableBook(
|
||||
val book: Pair<Book, CoverImage?>?,
|
||||
val book: Book?,
|
||||
val coverImage: CoverImage? = null,
|
||||
val text: List<StringWithId> = emptyList(),
|
||||
val fileName: String?,
|
||||
val message: UIText?
|
||||
) {
|
||||
class NotNull(book: Pair<Book, CoverImage?>) : NullableBook(book, null, null)
|
||||
class NotNull(
|
||||
book: Book,
|
||||
coverImage: CoverImage?,
|
||||
text: List<StringWithId>
|
||||
) : NullableBook(
|
||||
book = book,
|
||||
text = text,
|
||||
coverImage = coverImage,
|
||||
fileName = null,
|
||||
message = null
|
||||
)
|
||||
|
||||
class Null(
|
||||
fileName: String,
|
||||
message: UIText?
|
||||
) : NullableBook(null, fileName, message)
|
||||
) : NullableBook(null, text = emptyList(), fileName = fileName, message = message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,20 +23,23 @@ interface BookRepository {
|
|||
ids: List<Int>
|
||||
): List<Book>
|
||||
|
||||
suspend fun getBookTextById(
|
||||
suspend fun getBookText(
|
||||
textPath: String
|
||||
): List<StringWithId>
|
||||
|
||||
suspend fun insertBooks(
|
||||
books: List<Pair<Book, CoverImage?>>
|
||||
suspend fun insertBook(
|
||||
book: Book,
|
||||
coverImage: CoverImage?,
|
||||
text: List<StringWithId>
|
||||
): Boolean
|
||||
|
||||
suspend fun updateBooks(
|
||||
books: List<Book>
|
||||
)
|
||||
|
||||
suspend fun updateBooksWithText(
|
||||
books: List<Book>
|
||||
suspend fun updateBookWithText(
|
||||
book: Book,
|
||||
text: List<StringWithId>
|
||||
): Boolean
|
||||
|
||||
suspend fun updateCoverImageOfBook(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ import javax.inject.Inject
|
|||
class GetText @Inject constructor(private val repository: BookRepository) {
|
||||
|
||||
suspend fun execute(textPath: String): List<StringWithId> {
|
||||
return repository.getBookTextById(textPath = textPath)
|
||||
return repository.getBookText(textPath = textPath)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ua.acclorite.book_story.domain.use_case
|
||||
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
import ua.acclorite.book_story.domain.model.StringWithId
|
||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||
import ua.acclorite.book_story.domain.util.CoverImage
|
||||
import javax.inject.Inject
|
||||
|
||||
class InsertBook @Inject constructor(private val repository: BookRepository) {
|
||||
suspend fun execute(book: Book, coverImage: CoverImage?, text: List<StringWithId>): Boolean {
|
||||
return repository.insertBook(book, coverImage, text)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package ua.acclorite.book_story.domain.use_case
|
||||
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||
import ua.acclorite.book_story.domain.util.CoverImage
|
||||
import javax.inject.Inject
|
||||
|
||||
class InsertBooks @Inject constructor(private val repository: BookRepository) {
|
||||
suspend fun execute(books: List<Pair<Book, CoverImage?>>): Boolean {
|
||||
return repository.insertBooks(books)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ua.acclorite.book_story.domain.use_case
|
||||
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
import ua.acclorite.book_story.domain.model.StringWithId
|
||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class UpdateBookWithText @Inject constructor(private val repository: BookRepository) {
|
||||
|
||||
suspend fun execute(book: Book, text: List<StringWithId>): Boolean {
|
||||
return repository.updateBookWithText(book, text)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package ua.acclorite.book_story.domain.use_case
|
||||
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class UpdateBooksWithText @Inject constructor(private val repository: BookRepository) {
|
||||
|
||||
suspend fun execute(books: List<Book>): Boolean {
|
||||
return repository.updateBooksWithText(books)
|
||||
}
|
||||
}
|
||||
|
|
@ -244,21 +244,17 @@ object Constants {
|
|||
)
|
||||
|
||||
val EMPTY_BOOK = Book(
|
||||
-1,
|
||||
"",
|
||||
UIText.StringValue(""),
|
||||
null,
|
||||
"",
|
||||
emptyList(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0f,
|
||||
null,
|
||||
"",
|
||||
null,
|
||||
Category.READING,
|
||||
null
|
||||
id = -1,
|
||||
title = "",
|
||||
author = UIText.StringValue(""),
|
||||
description = null,
|
||||
textPath = "",
|
||||
filePath = "",
|
||||
coverImage = null,
|
||||
scrollIndex = 0,
|
||||
scrollOffset = 0,
|
||||
progress = 0f,
|
||||
lastOpened = null,
|
||||
category = Category.READING
|
||||
)
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp
|
|||
import ua.acclorite.book_story.R
|
||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
|
||||
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoState
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
|
@ -46,7 +47,10 @@ fun BookInfoDetailsBottomSheet(
|
|||
}
|
||||
|
||||
val sizeBytes = remember {
|
||||
state.value.book.file?.length() ?: 0
|
||||
val file = File(state.value.book.filePath)
|
||||
if (file.exists()) {
|
||||
file.length()
|
||||
} else 0
|
||||
}
|
||||
|
||||
val fileSizeKB = remember {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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.StringWithId
|
||||
import ua.acclorite.book_story.presentation.data.Navigator
|
||||
|
||||
@Immutable
|
||||
|
|
@ -57,7 +58,7 @@ sealed class BookInfoEvent {
|
|||
data object OnDismissConfirmUpdateDialog : BookInfoEvent()
|
||||
|
||||
data class OnShowConfirmUpdateDialog(
|
||||
val updatedBook: Book,
|
||||
val updatedBook: Pair<Book, List<StringWithId>>,
|
||||
val authorUpdated: Boolean,
|
||||
val descriptionUpdated: Boolean,
|
||||
val textUpdated: Boolean,
|
||||
|
|
|
|||
|
|
@ -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.StringWithId
|
||||
import ua.acclorite.book_story.domain.util.Constants
|
||||
|
||||
@Immutable
|
||||
|
|
@ -12,7 +13,7 @@ data class BookInfoState(
|
|||
val isLoadingUpdate: Boolean = false,
|
||||
val isRefreshing: Boolean = false,
|
||||
val showConfirmUpdateDialog: Boolean = false,
|
||||
val updatedBook: Book? = null,
|
||||
val updatedBook: Pair<Book, List<StringWithId>>? = null,
|
||||
val authorChanged: Boolean = false,
|
||||
val descriptionChanged: Boolean = false,
|
||||
val textChanged: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -26,12 +26,13 @@ import ua.acclorite.book_story.domain.use_case.GetBookFromFile
|
|||
import ua.acclorite.book_story.domain.use_case.GetBooksById
|
||||
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.UpdateBookWithText
|
||||
import ua.acclorite.book_story.domain.use_case.UpdateBooks
|
||||
import ua.acclorite.book_story.domain.use_case.UpdateBooksWithText
|
||||
import ua.acclorite.book_story.domain.use_case.UpdateCoverImageOfBook
|
||||
import ua.acclorite.book_story.presentation.data.Argument
|
||||
import ua.acclorite.book_story.presentation.data.Navigator
|
||||
import ua.acclorite.book_story.presentation.data.Screen
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
class BookInfoViewModel @Inject constructor(
|
||||
private val updateBooks: UpdateBooks,
|
||||
private val updateBooksWithText: UpdateBooksWithText,
|
||||
private val updateBookWithText: UpdateBookWithText,
|
||||
private val updateCoverImageOfBook: UpdateCoverImageOfBook,
|
||||
private val insertHistory: InsertHistory,
|
||||
private val deleteBooks: DeleteBooks,
|
||||
|
|
@ -302,7 +303,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
yield()
|
||||
if (_state.value.book.file == null) {
|
||||
if (!File(_state.value.book.filePath).exists()) {
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowSnackbar(
|
||||
text = event.context.getString(
|
||||
|
|
@ -334,12 +335,13 @@ class BookInfoViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
yield()
|
||||
val nullableBook = getBookFromFile.execute(_state.value.book.file!!)
|
||||
val updatedBook = getBookFromFile.execute(File(_state.value.book.filePath))
|
||||
yield()
|
||||
if (nullableBook is NullableBook.Null) {
|
||||
|
||||
if (updatedBook is NullableBook.Null) {
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowSnackbar(
|
||||
text = nullableBook.message?.asString(event.context)
|
||||
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 = {
|
||||
|
|
@ -365,7 +367,6 @@ class BookInfoViewModel @Inject constructor(
|
|||
}
|
||||
yield()
|
||||
|
||||
val updatedBook = nullableBook.book?.first ?: return@launch
|
||||
val book = _state.value.book
|
||||
|
||||
var authorUpdated = false
|
||||
|
|
@ -373,20 +374,19 @@ class BookInfoViewModel @Inject constructor(
|
|||
var textUpdated = false
|
||||
|
||||
if (
|
||||
updatedBook.author.asString(event.context) !=
|
||||
updatedBook.book!!.author.asString(event.context) !=
|
||||
book.author.asString(event.context)
|
||||
) {
|
||||
authorUpdated = true
|
||||
}
|
||||
if (updatedBook.description != book.description) {
|
||||
if (updatedBook.book.description != book.description) {
|
||||
descriptionUpdated = true
|
||||
}
|
||||
if (
|
||||
updatedBook.text.map { it.line } !=
|
||||
book.text.ifEmpty {
|
||||
getText.execute(book.textPath)
|
||||
}.map { it.line }
|
||||
) {
|
||||
|
||||
val updatedText = updatedBook.text
|
||||
val text = getText.execute(book.textPath)
|
||||
|
||||
if (updatedText.map { it.line } != text.map { it.line }) {
|
||||
textUpdated = true
|
||||
}
|
||||
|
||||
|
|
@ -412,7 +412,7 @@ class BookInfoViewModel @Inject constructor(
|
|||
yield()
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowConfirmUpdateDialog(
|
||||
updatedBook = updatedBook,
|
||||
updatedBook = updatedBook.book to updatedBook.text,
|
||||
authorUpdated = authorUpdated,
|
||||
descriptionUpdated = descriptionUpdated,
|
||||
textUpdated = textUpdated
|
||||
|
|
@ -492,36 +492,29 @@ class BookInfoViewModel @Inject constructor(
|
|||
val updatedBook = _state.value.updatedBook ?: return@launch
|
||||
|
||||
val author = if (_state.value.authorChanged) {
|
||||
updatedBook.author
|
||||
updatedBook.first.author
|
||||
} else {
|
||||
book.author
|
||||
}
|
||||
val description = if (_state.value.descriptionChanged) {
|
||||
updatedBook.description
|
||||
updatedBook.first.description
|
||||
} else {
|
||||
book.description
|
||||
}
|
||||
val text = if (_state.value.textChanged) {
|
||||
updatedBook.text
|
||||
} else {
|
||||
book.text
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
book = it.book.copy(
|
||||
author = author,
|
||||
description = description,
|
||||
text = text
|
||||
description = description
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (_state.value.textChanged) {
|
||||
val isSuccess = updateBooksWithText.execute(
|
||||
listOf(
|
||||
_state.value.book
|
||||
)
|
||||
val isSuccess = updateBookWithText.execute(
|
||||
book = _state.value.book,
|
||||
text = updatedBook.second
|
||||
)
|
||||
|
||||
if (!isSuccess) {
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ fun BrowseAddingDialogItem(result: Pair<NullableBook, Selected>, onClick: (Boole
|
|||
Modifier.weight(0.85f)
|
||||
) {
|
||||
Text(
|
||||
text = result.first.book!!.first.title,
|
||||
text = result.first.book!!.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = result.first.book!!.first.author.asString(),
|
||||
text = result.first.book!!.author.asString(),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import kotlinx.coroutines.yield
|
|||
import ua.acclorite.book_story.domain.model.NullableBook
|
||||
import ua.acclorite.book_story.domain.use_case.GetBooksFromFiles
|
||||
import ua.acclorite.book_story.domain.use_case.GetFilesFromDevice
|
||||
import ua.acclorite.book_story.domain.use_case.InsertBooks
|
||||
import ua.acclorite.book_story.domain.use_case.InsertBook
|
||||
import ua.acclorite.book_story.domain.util.Resource
|
||||
import ua.acclorite.book_story.presentation.data.Screen
|
||||
import javax.inject.Inject
|
||||
|
|
@ -33,7 +33,7 @@ import javax.inject.Inject
|
|||
class BrowseViewModel @Inject constructor(
|
||||
private val getBooksFromFiles: GetBooksFromFiles,
|
||||
private val getFilesFromDevice: GetFilesFromDevice,
|
||||
private val insertBooks: InsertBooks
|
||||
private val insertBook: InsertBook
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(BrowseState())
|
||||
|
|
@ -369,13 +369,28 @@ class BrowseViewModel @Inject constructor(
|
|||
val booksToInsert = _state.value.selectedBooks
|
||||
.filter { it.first is NullableBook.NotNull }
|
||||
.filter { it.second }
|
||||
.map { it.first.book!! }
|
||||
.map { it.first }
|
||||
|
||||
if (booksToInsert.isEmpty()) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (!insertBooks.execute(booksToInsert)) {
|
||||
val failed = booksToInsert.any {
|
||||
!insertBook.execute(
|
||||
it.book!!,
|
||||
it.coverImage,
|
||||
it.text
|
||||
)
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
showAddingDialog = false
|
||||
)
|
||||
}
|
||||
onEvent(BrowseEvent.OnLoadList)
|
||||
onEvent(BrowseEvent.OnClearSelectedFiles)
|
||||
event.onFailed()
|
||||
return@launch
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,14 +201,14 @@ private fun ReaderScreen(
|
|||
snapshotFlow {
|
||||
listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset
|
||||
}.debounce(500).collectLatest { items ->
|
||||
if (!loading.value && state.value.book.text.isNotEmpty() && listState.layoutInfo.totalItemsCount > 0) {
|
||||
if (!loading.value && state.value.text.isNotEmpty() && listState.layoutInfo.totalItemsCount > 0) {
|
||||
val lastVisibleItemIndex = listState.layoutInfo.visibleItemsInfo.last().index
|
||||
|
||||
val progress = if (items.first > 0) {
|
||||
if (lastVisibleItemIndex >= (listState.layoutInfo.totalItemsCount - 1)) {
|
||||
1f
|
||||
} else {
|
||||
(items.first.toFloat() / (state.value.book.text.lastIndex).toFloat())
|
||||
(items.first.toFloat() / (state.value.text.lastIndex).toFloat())
|
||||
}
|
||||
} else {
|
||||
0f
|
||||
|
|
@ -338,7 +338,7 @@ private fun ReaderScreen(
|
|||
}
|
||||
)
|
||||
) {
|
||||
if (state.value.book.text.isNotEmpty()) {
|
||||
if (state.value.text.isNotEmpty()) {
|
||||
item {
|
||||
DisableSelection {
|
||||
ReaderStartItem(state = state)
|
||||
|
|
@ -347,9 +347,9 @@ private fun ReaderScreen(
|
|||
}
|
||||
|
||||
customItemsIndexed(
|
||||
state.value.book.text, key = { key -> key.id }
|
||||
state.value.text, key = { key -> key.id }
|
||||
) { index, line ->
|
||||
val text = remember(mainState.value.paragraphIndentation) {
|
||||
val text = remember(mainState.value.paragraphIndentation, line) {
|
||||
"${if (mainState.value.paragraphIndentation!!) " " else ""}${line.line}"
|
||||
}
|
||||
|
||||
|
|
@ -361,7 +361,7 @@ private fun ReaderScreen(
|
|||
top = if (index == 0) 18.dp else 0.dp,
|
||||
start = sidePadding,
|
||||
end = sidePadding,
|
||||
bottom = if (index == state.value.book.text.lastIndex) 18.dp
|
||||
bottom = if (index == state.value.text.lastIndex) 18.dp
|
||||
else paragraphHeight
|
||||
)
|
||||
) {
|
||||
|
|
@ -377,7 +377,7 @@ private fun ReaderScreen(
|
|||
}
|
||||
}
|
||||
|
||||
if (state.value.book.text.isNotEmpty()) {
|
||||
if (state.value.text.isNotEmpty()) {
|
||||
item {
|
||||
DisableSelection {
|
||||
ReaderEndItem(
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ fun ReaderEndItem(
|
|||
Text(
|
||||
stringResource(
|
||||
id = R.string.letters_and_words,
|
||||
state.value.book.letters,
|
||||
state.value.book.words
|
||||
state.value.letters,
|
||||
state.value.words
|
||||
),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@ package ua.acclorite.book_story.presentation.screens.reader.data
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
import ua.acclorite.book_story.domain.model.StringWithId
|
||||
import ua.acclorite.book_story.domain.util.Constants
|
||||
import ua.acclorite.book_story.domain.util.UIText
|
||||
|
||||
@Immutable
|
||||
data class ReaderState(
|
||||
val book: Book = Constants.EMPTY_BOOK,
|
||||
val text: List<StringWithId> = emptyList(),
|
||||
val words: Int = 0,
|
||||
val letters: Int = 0,
|
||||
|
||||
val errorMessage: UIText? = null,
|
||||
|
||||
val showMenu: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -57,11 +57,6 @@ class ReaderViewModel @Inject constructor(
|
|||
|
||||
is ReaderEvent.OnLoadText -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (
|
||||
_state.value.book.text.isEmpty() ||
|
||||
_state.value.book.letters < 1 ||
|
||||
_state.value.book.words < 1
|
||||
) {
|
||||
val text = getText.execute(_state.value.book.textPath)
|
||||
|
||||
if (text.isEmpty()) {
|
||||
|
|
@ -85,13 +80,10 @@ class ReaderViewModel @Inject constructor(
|
|||
|
||||
_state.update {
|
||||
it.copy(
|
||||
book = it.book.copy(
|
||||
text = text,
|
||||
letters = letters,
|
||||
words = words
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val history = _state.value.book.id.let {
|
||||
|
|
@ -111,13 +103,7 @@ class ReaderViewModel @Inject constructor(
|
|||
updateBooks.execute(
|
||||
listOf(_state.value.book)
|
||||
)
|
||||
event.refreshList(
|
||||
_state.value.book.copy(
|
||||
text = emptyList(),
|
||||
letters = 0,
|
||||
words = 0
|
||||
)
|
||||
)
|
||||
event.refreshList(_state.value.book)
|
||||
|
||||
viewModelScope.launch {
|
||||
snapshotFlow {
|
||||
|
|
@ -126,7 +112,7 @@ class ReaderViewModel @Inject constructor(
|
|||
val index = _state.value.book.scrollIndex
|
||||
val offset = _state.value.book.scrollOffset
|
||||
|
||||
if (itemsCount >= _state.value.book.text.size) {
|
||||
if (itemsCount >= _state.value.text.size) {
|
||||
if (index > 0 || offset > 0) {
|
||||
var loaded = false
|
||||
for (i in 1..100) {
|
||||
|
|
@ -213,7 +199,7 @@ class ReaderViewModel @Inject constructor(
|
|||
if (lastVisibleItemIndex >= (event.listState.layoutInfo.totalItemsCount - 1)) {
|
||||
1f
|
||||
} else {
|
||||
(firstVisibleItemIndex.toFloat() / (_state.value.book.text.lastIndex)
|
||||
(firstVisibleItemIndex.toFloat() / (_state.value.text.lastIndex)
|
||||
.toFloat())
|
||||
}
|
||||
} else {
|
||||
|
|
@ -237,13 +223,7 @@ class ReaderViewModel @Inject constructor(
|
|||
event.navigator.putArgument(
|
||||
Argument("book", _state.value.book.id)
|
||||
)
|
||||
event.refreshList(
|
||||
_state.value.book.copy(
|
||||
text = emptyList(),
|
||||
letters = 0,
|
||||
words = 0
|
||||
)
|
||||
)
|
||||
event.refreshList(_state.value.book)
|
||||
|
||||
insetsController.show(WindowInsetsCompat.Type.systemBars())
|
||||
event.navigate(event.navigator)
|
||||
|
|
@ -252,7 +232,7 @@ class ReaderViewModel @Inject constructor(
|
|||
|
||||
is ReaderEvent.OnScroll -> {
|
||||
viewModelScope.launch {
|
||||
val scrollTo = (_state.value.book.text.size * event.progress).roundToInt()
|
||||
val scrollTo = (_state.value.text.size * event.progress).roundToInt()
|
||||
|
||||
event.listState.scrollToItem(
|
||||
scrollTo
|
||||
|
|
@ -281,13 +261,7 @@ class ReaderViewModel @Inject constructor(
|
|||
_state.value.book.id
|
||||
)
|
||||
)
|
||||
event.refreshList(
|
||||
_state.value.book.copy(
|
||||
text = emptyList(),
|
||||
letters = 0,
|
||||
words = 0
|
||||
)
|
||||
)
|
||||
event.refreshList(_state.value.book)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -336,11 +310,7 @@ class ReaderViewModel @Inject constructor(
|
|||
updateBooks.execute(listOf(_state.value.book))
|
||||
|
||||
event.onUpdateCategories(
|
||||
_state.value.book.copy(
|
||||
text = emptyList(),
|
||||
letters = 0,
|
||||
words = 0
|
||||
)
|
||||
_state.value.book.copy()
|
||||
)
|
||||
event.updatePage(
|
||||
Category.entries.dropLastWhile {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue