diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6ae4e68a..d3711c0e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,6 +25,9 @@ android { } buildTypes { + getByName("debug") { + applicationIdSuffix = ".debug" + } getByName("release") { isMinifyEnabled = true isShrinkResources = true diff --git a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt index dbc32a88..2f1e2c9c 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/mapper/book/BookMapperImpl.kt @@ -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, diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt index 3aa6aec8..02d041f0 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubFileParser.kt @@ -16,7 +16,7 @@ import javax.inject.Inject class EpubFileParser @Inject constructor() : FileParser { override suspend fun parse(file: File): Pair? { - 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], diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt index 71e92f4a..e6d797ff 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfFileParser.kt @@ -15,7 +15,7 @@ import javax.inject.Inject class PdfFileParser @Inject constructor(private val application: Application) : FileParser { override suspend fun parse(file: File): Pair? { - 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], diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt index 12cb4493..69c97838 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtFileParser.kt @@ -12,7 +12,7 @@ import javax.inject.Inject class TxtFileParser @Inject constructor() : FileParser { override suspend fun parse(file: File): Pair? { - 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], diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt index b8b22e77..b222e180 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt @@ -88,7 +88,7 @@ class BookRepositoryImpl @Inject constructor( } } - override suspend fun getBookTextById(textPath: String): List { + override suspend fun getBookText(textPath: String): List { 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> + override suspend fun insertBook( + book: Book, + coverImage: CoverImage?, + text: List ): Boolean { val filesDir = application.filesDir val coversDir = File(filesDir, "covers") @@ -119,67 +121,64 @@ class BookRepositoryImpl @Inject constructor( booksDir.mkdirs() } - val booksWithCoverAndText = books.map { - var coverUri = "" - val textUri: String + var coverUri = "" + val textUri: String - if (it.first.text.isEmpty()) { - return false + if (text.isEmpty()) { + return false + } + + try { + textUri = "${UUID.randomUUID()}.txt" + val textPath = File(booksDir, textUri) + + withContext(Dispatchers.IO) { + FileOutputStream(textPath).use { stream -> + text.forEach { line -> + stream.write(line.line.toByteArray()) + stream.write(System.lineSeparator().toByteArray()) + } + } } + } catch (e: Exception) { + e.printStackTrace() + return false + } + if (coverImage != null) { try { - textUri = "${UUID.randomUUID()}.txt" - val text = File(booksDir, textUri) + coverUri = "${UUID.randomUUID()}.webp" + val cover = File(coversDir, coverUri) withContext(Dispatchers.IO) { - FileOutputStream(text).use { stream -> - it.first.text.forEach { line -> - stream.write(line.line.toByteArray()) - stream.write(System.lineSeparator().toByteArray()) + FileOutputStream(cover).use { stream -> + if ( + !coverImage.copy(Bitmap.Config.RGB_565, false).compress( + Bitmap.CompressFormat.WEBP, + 20, + stream + ) + ) { + throw Exception("Couldn't save cover image") } } } } catch (e: Exception) { + coverUri = "" e.printStackTrace() - return false } - - if (it.second != null) { - try { - coverUri = "${UUID.randomUUID()}.webp" - val cover = File(coversDir, coverUri) - - withContext(Dispatchers.IO) { - FileOutputStream(cover).use { stream -> - if ( - !it.second!!.copy(Bitmap.Config.RGB_565, false).compress( - Bitmap.CompressFormat.WEBP, - 20, - stream - ) - ) { - throw Exception("Couldn't save cover image") - } - } - } - } catch (e: Exception) { - coverUri = "" - e.printStackTrace() - } - } - - - val book = it.first.copy( - textPath = "$booksDir/$textUri", - coverImage = if (coverUri.isNotBlank()) { - Uri.fromFile(File("$coversDir/$coverUri")) - } else null - ) - - bookMapper.toBookEntity(book) } - database.insertBooks(booksWithCoverAndText) + + val updatedBook = book.copy( + textPath = "$booksDir/$textUri", + coverImage = if (coverUri.isNotBlank()) { + Uri.fromFile(File("$coversDir/$coverUri")) + } else null + ) + + 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): Boolean { + override suspend fun updateBookWithText(book: Book, text: List): Boolean { // without cover image val filesDir = application.filesDir val booksDir = File(filesDir, "books") @@ -207,55 +206,53 @@ class BookRepositoryImpl @Inject constructor( booksDir.mkdirs() } - val booksWithText = books.map { - val textUri: String - val bookEntity = database.findBookById(it.id) + val textUri: String + val bookEntity = database.findBookById(book.id) - if (it.text.isEmpty()) { - return false - } + if (text.isEmpty()) { + return false + } - try { - textUri = "${UUID.randomUUID()}.txt" - val text = File(booksDir, textUri) + try { + textUri = "${UUID.randomUUID()}.txt" + val textPath = File(booksDir, textUri) - withContext(Dispatchers.IO) { - FileOutputStream(text).use { stream -> - it.text.forEach { line -> - stream.write(line.line.toByteArray()) - stream.write(System.lineSeparator().toByteArray()) - } + withContext(Dispatchers.IO) { + FileOutputStream(textPath).use { stream -> + text.forEach { line -> + stream.write(line.line.toByteArray()) + stream.write(System.lineSeparator().toByteArray()) } } + } + } catch (e: Exception) { + e.printStackTrace() + return false + } + + if (book.textPath.isNotBlank()) { + try { + val fileToDelete = File( + book.textPath + ) + + if (fileToDelete.exists()) { + fileToDelete.delete() + } } catch (e: Exception) { e.printStackTrace() - return false } - - if (it.textPath.isNotBlank()) { - try { - val fileToDelete = File( - it.textPath - ) - - if (fileToDelete.exists()) { - fileToDelete.delete() - } - } catch (e: Exception) { - e.printStackTrace() - } - } - - bookMapper.toBookEntity( - it.copy( - textPath = "$booksDir/$textUri", - coverImage = if (bookEntity.image != null) Uri.parse(bookEntity.image) else null - ) - ) } + 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) { diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt index 6e89bbdf..f6d5ba17 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/Book.kt @@ -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 = 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? ) diff --git a/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt index 39ab8b1b..add5bfe2 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt @@ -6,13 +6,26 @@ import ua.acclorite.book_story.domain.util.UIText @Immutable sealed class NullableBook( - val book: Pair?, + val book: Book?, + val coverImage: CoverImage? = null, + val text: List = emptyList(), val fileName: String?, val message: UIText? ) { - class NotNull(book: Pair) : NullableBook(book, null, null) + class NotNull( + book: Book, + coverImage: CoverImage?, + text: List + ) : 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) } diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt index 8cf71987..a2276cf0 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt @@ -23,20 +23,23 @@ interface BookRepository { ids: List ): List - suspend fun getBookTextById( + suspend fun getBookText( textPath: String ): List - suspend fun insertBooks( - books: List> + suspend fun insertBook( + book: Book, + coverImage: CoverImage?, + text: List ): Boolean suspend fun updateBooks( books: List ) - suspend fun updateBooksWithText( - books: List + suspend fun updateBookWithText( + book: Book, + text: List ): Boolean suspend fun updateCoverImageOfBook( diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt index 34ef9c7a..db3d6d3e 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class GetText @Inject constructor(private val repository: BookRepository) { suspend fun execute(textPath: String): List { - return repository.getBookTextById(textPath = textPath) + return repository.getBookText(textPath = textPath) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBook.kt new file mode 100644 index 00000000..87db836d --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBook.kt @@ -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): Boolean { + return repository.insertBook(book, coverImage, text) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBooks.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBooks.kt deleted file mode 100644 index 0ec0fc5b..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertBooks.kt +++ /dev/null @@ -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>): Boolean { - return repository.insertBooks(books) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBookWithText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBookWithText.kt new file mode 100644 index 00000000..e44ac5a8 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBookWithText.kt @@ -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): Boolean { + return repository.updateBookWithText(book, text) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBooksWithText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBooksWithText.kt deleted file mode 100644 index 182fccab..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBooksWithText.kt +++ /dev/null @@ -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): Boolean { - return repository.updateBooksWithText(books) - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt index d47c932d..6d9f0763 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/util/Constants.kt @@ -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 ) } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt index 159dadd2..34ffc8dc 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/components/details_bottom_sheet/BookInfoDetailsBottomSheet.kt @@ -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 { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt index dd3c91c3..95b5ee49 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoEvent.kt @@ -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>, val authorUpdated: Boolean, val descriptionUpdated: Boolean, val textUpdated: Boolean, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt index 14e000e6..1f878d59 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoState.kt @@ -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>? = null, val authorChanged: Boolean = false, val descriptionChanged: Boolean = false, val textChanged: Boolean = false, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt index 525bb554..4abf5ad6 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt @@ -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) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt index e23d61ac..a3050986 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt @@ -44,14 +44,14 @@ fun BrowseAddingDialogItem(result: Pair, 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, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt index 54a75e92..b326e925 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/data/BrowseViewModel.kt @@ -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 } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt index 21982518..2c7cc93e 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt @@ -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( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt index ae5f1610..6742b490 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderEndItem.kt @@ -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, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt index 2d86f0b5..e0846923 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt @@ -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 = emptyList(), + val words: Int = 0, + val letters: Int = 0, + val errorMessage: UIText? = null, val showMenu: Boolean = false, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt index bb351f3d..bae67f2a 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt @@ -57,41 +57,33 @@ 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) + val text = getText.execute(_state.value.book.textPath) - if (text.isEmpty()) { - event.onTextIsEmpty() + if (text.isEmpty()) { + event.onTextIsEmpty() + } + + val textAsLine = text.joinToString( + separator = "\n", + transform = { + it.line } + ) - val textAsLine = text.joinToString( - separator = "\n", - transform = { - it.line - } + val letters = textAsLine + .replace("\n", "") + .length + val words = textAsLine + .replace("\n", " ") + .split("\\s+".toRegex()) + .size + + _state.update { + it.copy( + text = text, + letters = letters, + words = words ) - - val letters = textAsLine - .replace("\n", "") - .length - val words = textAsLine - .replace("\n", " ") - .split("\\s+".toRegex()) - .size - - _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 {