diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/FileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/FileParser.kt index 7d7ee00a..e599de97 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/FileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/FileParser.kt @@ -1,11 +1,10 @@ package ua.acclorite.book_story.data.parser -import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.domain.util.CoverImage +import ua.acclorite.book_story.domain.model.BookWithCover import java.io.File interface FileParser { - suspend fun parse(file: File): Pair? + suspend fun parse(file: File): BookWithCover? } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/FileParserImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/FileParserImpl.kt index 69426e34..9fafd716 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/FileParserImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/FileParserImpl.kt @@ -7,8 +7,7 @@ import ua.acclorite.book_story.data.parser.htm.HtmFileParser import ua.acclorite.book_story.data.parser.html.HtmlFileParser import ua.acclorite.book_story.data.parser.pdf.PdfFileParser import ua.acclorite.book_story.data.parser.txt.TxtFileParser -import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.domain.util.CoverImage +import ua.acclorite.book_story.domain.model.BookWithCover import java.io.File import javax.inject.Inject @@ -22,7 +21,7 @@ class FileParserImpl @Inject constructor( private val htmlFileParser: HtmlFileParser, private val htmFileParser: HtmFileParser, ) : FileParser { - override suspend fun parse(file: File): Pair? { + override suspend fun parse(file: File): BookWithCover? { if (!file.exists()) { Log.e(FILE_PARSER, "File does not exist.") return null 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 a590421e..f4ddf347 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 @@ -8,8 +8,8 @@ import org.jsoup.Jsoup import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithCover import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.domain.util.CoverImage import ua.acclorite.book_story.domain.util.UIText import java.io.File import java.util.zip.ZipFile @@ -17,9 +17,9 @@ import javax.inject.Inject class EpubFileParser @Inject constructor() : FileParser { - override suspend fun parse(file: File): Pair? { + override suspend fun parse(file: File): BookWithCover? { return try { - var book: Pair? = null + var book: BookWithCover? = null withContext(Dispatchers.IO) { ZipFile(file).use { zip -> @@ -71,19 +71,22 @@ class EpubFileParser @Inject constructor() : FileParser { .firstOrNull()?.attr("href") } - book = Book( - title = title, - author = author, - description = description, - textPath = "", - scrollIndex = 0, - scrollOffset = 0, - progress = 0f, - filePath = file.path, - lastOpened = null, - category = Category.entries[0], - coverImage = null - ) to extractCoverImageBitmap(file, coverImage) + book = BookWithCover( + book = Book( + title = title, + author = author, + description = description, + textPath = "", + scrollIndex = 0, + scrollOffset = 0, + progress = 0f, + filePath = file.path, + lastOpened = null, + category = Category.entries[0], + coverImage = null + ), + coverImage = extractCoverImageBitmap(file, coverImage) + ) } } book diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2FileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2FileParser.kt index 5a1c5b88..edc70bb8 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2FileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2FileParser.kt @@ -7,8 +7,8 @@ import org.w3c.dom.Element import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithCover import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.domain.util.CoverImage import ua.acclorite.book_story.domain.util.UIText import java.io.File import javax.inject.Inject @@ -16,7 +16,7 @@ import javax.xml.parsers.DocumentBuilderFactory class Fb2FileParser @Inject constructor() : FileParser { - override suspend fun parse(file: File): Pair? { + override suspend fun parse(file: File): BookWithCover? { return try { val factory = DocumentBuilderFactory.newInstance() val builder = factory.newDocumentBuilder() @@ -50,19 +50,22 @@ class Fb2FileParser @Inject constructor() : FileParser { val descriptionFromFile = extractElementContent(document, "annotation") - Book( - title = title, - author = author, - description = descriptionFromFile, - textPath = "", - scrollIndex = 0, - scrollOffset = 0, - progress = 0f, - filePath = file.path, - lastOpened = null, - category = Category.entries[0], + BookWithCover( + book = Book( + title = title, + author = author, + description = descriptionFromFile, + textPath = "", + scrollIndex = 0, + scrollOffset = 0, + progress = 0f, + filePath = file.path, + lastOpened = null, + category = Category.entries[0], + coverImage = null + ), coverImage = null - ) to null + ) } catch (e: Exception) { e.printStackTrace() null diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/htm/HtmFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/htm/HtmFileParser.kt index cf08cd82..1397c639 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/htm/HtmFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/htm/HtmFileParser.kt @@ -4,15 +4,15 @@ import org.jsoup.Jsoup import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithCover import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.domain.util.CoverImage import ua.acclorite.book_story.domain.util.UIText import java.io.File import javax.inject.Inject class HtmFileParser @Inject constructor() : FileParser { - override suspend fun parse(file: File): Pair? { + override suspend fun parse(file: File): BookWithCover? { return try { val document = Jsoup.parse(file) @@ -22,19 +22,22 @@ class HtmFileParser @Inject constructor() : FileParser { } } - Book( - title = title, - author = UIText.StringResource(R.string.unknown_author), - description = null, - textPath = "", - scrollIndex = 0, - scrollOffset = 0, - progress = 0f, - filePath = file.path, - lastOpened = null, - category = Category.entries[0], + BookWithCover( + book = Book( + title = title, + author = UIText.StringResource(R.string.unknown_author), + description = null, + textPath = "", + scrollIndex = 0, + scrollOffset = 0, + progress = 0f, + filePath = file.path, + lastOpened = null, + category = Category.entries[0], + coverImage = null + ), coverImage = null - ) to null + ) } catch (e: Exception) { e.printStackTrace() null diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/html/HtmlFileParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/html/HtmlFileParser.kt index 5c686f75..4f332e7e 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/html/HtmlFileParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/html/HtmlFileParser.kt @@ -4,15 +4,15 @@ import org.jsoup.Jsoup import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithCover import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.domain.util.CoverImage import ua.acclorite.book_story.domain.util.UIText import java.io.File import javax.inject.Inject class HtmlFileParser @Inject constructor() : FileParser { - override suspend fun parse(file: File): Pair? { + override suspend fun parse(file: File): BookWithCover? { return try { val document = Jsoup.parse(file) @@ -22,19 +22,22 @@ class HtmlFileParser @Inject constructor() : FileParser { } } - Book( - title = title, - author = UIText.StringResource(R.string.unknown_author), - description = null, - textPath = "", - scrollIndex = 0, - scrollOffset = 0, - progress = 0f, - filePath = file.path, - lastOpened = null, - category = Category.entries[0], + BookWithCover( + book = Book( + title = title, + author = UIText.StringResource(R.string.unknown_author), + description = null, + textPath = "", + scrollIndex = 0, + scrollOffset = 0, + progress = 0f, + filePath = file.path, + lastOpened = null, + category = Category.entries[0], + coverImage = null + ), coverImage = null - ) to null + ) } catch (e: Exception) { e.printStackTrace() null 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 62294708..c7e0fcda 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 @@ -6,15 +6,15 @@ import com.tom_roush.pdfbox.pdmodel.PDDocument import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithCover import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.domain.util.CoverImage import ua.acclorite.book_story.domain.util.UIText import java.io.File import javax.inject.Inject class PdfFileParser @Inject constructor(private val application: Application) : FileParser { - override suspend fun parse(file: File): Pair? { + override suspend fun parse(file: File): BookWithCover? { return try { PDFBoxResourceLoader.init(application) @@ -29,19 +29,22 @@ class PdfFileParser @Inject constructor(private val application: Application) : document.close() - Book( - title = title, - author = author, - description = description, - textPath = "", - scrollIndex = 0, - scrollOffset = 0, - progress = 0f, - filePath = file.path, - lastOpened = null, - category = Category.entries[0], + BookWithCover( + book = Book( + title = title, + author = author, + description = description, + textPath = "", + scrollIndex = 0, + scrollOffset = 0, + progress = 0f, + filePath = file.path, + lastOpened = null, + category = Category.entries[0], + coverImage = null + ), coverImage = null - ) to null + ) } catch (e: Exception) { e.printStackTrace() null 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 bcfb4aa8..cd344fdd 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 @@ -3,32 +3,35 @@ package ua.acclorite.book_story.data.parser.txt import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithCover import ua.acclorite.book_story.domain.model.Category -import ua.acclorite.book_story.domain.util.CoverImage import ua.acclorite.book_story.domain.util.UIText import java.io.File import javax.inject.Inject class TxtFileParser @Inject constructor() : FileParser { - override suspend fun parse(file: File): Pair? { + override suspend fun parse(file: File): BookWithCover? { return try { val title = file.nameWithoutExtension.trim() val author = UIText.StringResource(R.string.unknown_author) - Book( - title = title, - author = author, - description = null, - textPath = "", - scrollIndex = 0, - scrollOffset = 0, - progress = 0f, - filePath = file.path, - lastOpened = null, - category = Category.entries[0], + BookWithCover( + book = Book( + title = title, + author = author, + description = null, + textPath = "", + scrollIndex = 0, + scrollOffset = 0, + progress = 0f, + filePath = file.path, + lastOpened = null, + category = Category.entries[0], + coverImage = null + ), coverImage = null - ) to null + ) } catch (e: Exception) { e.printStackTrace() null 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 76bd7d74..9955d031 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 @@ -9,13 +9,9 @@ import android.provider.MediaStore import android.util.Log import androidx.datastore.preferences.core.Preferences import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import ua.acclorite.book_story.R import ua.acclorite.book_story.data.local.data_store.DataStore @@ -30,6 +26,8 @@ import ua.acclorite.book_story.data.parser.TextParser import ua.acclorite.book_story.data.remote.GithubAPI import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithText +import ua.acclorite.book_story.domain.model.BookWithTextAndCover import ua.acclorite.book_story.domain.model.ColorPreset import ua.acclorite.book_story.domain.model.History import ua.acclorite.book_story.domain.model.NullableBook @@ -51,6 +49,16 @@ import javax.inject.Singleton private const val GET_BOOK_FROM_FILE = "GET BOOK FROM FILE, REPOSITORY" private const val GET_TEXT = "GET TEXT, REPOSITORY" +private const val GET_BOOKS = "GET BOOKS, REPOSITORY" +private const val GET_BOOKS_BY_ID = "GET BOOKS, REPOSITORY" +private const val INSERT_BOOK = "INSERT BOOK, REPOSITORY" +private const val UPDATE_BOOK = "UPDATE BOOK, REPOSITORY" +private const val DELETE_BOOKS = "DELETE BOOKS, REPOSITORY" +private const val CAN_RESET_COVER = "CAN RESET COVER, REPOSITORY" +private const val RESET_COVER = "RESET COVER, REPOSITORY" +private const val GET_ALL_SETTINGS = "GET ALL SETTINGS, REPOSITORY" +private const val GET_FILES_FROM_DEVICE = "GET FILES FROM DEVICE, REPOSITORY" +private const val CHECK_FOR_UPDATES = "CHECK FOR UPDATES, REPOSITORY" @Suppress("DEPRECATION") @Singleton @@ -67,13 +75,18 @@ class BookRepositoryImpl @Inject constructor( private val colorPresetMapper: ColorPresetMapper, private val fileParser: FileParser, - private val textParser: TextParser - + private val textParser: TextParser, ) : BookRepository { + /** + * Get all books matching [query] from database. + * Empty [query] equals to all books. + */ override suspend fun getBooks(query: String): List { + Log.i(GET_BOOKS, "Searching for books with query: \"$query\".") val books = database.searchBooks(query) + Log.i(GET_BOOKS, "Found ${books.size} books.") return books.map { entity -> val book = bookMapper.toBook(entity) val lastHistory = database.getLatestHistoryForBook( @@ -86,7 +99,11 @@ class BookRepositoryImpl @Inject constructor( } } + /** + * Get all books that match given [ids]. + */ override suspend fun getBooksById(ids: List): List { + Log.i(GET_BOOKS_BY_ID, "Getting books with ids: $ids.") val books = database.findBooksById(ids) return books.map { entity -> @@ -128,26 +145,33 @@ class BookRepositoryImpl @Inject constructor( return lines } + /** + * Inserts book in database. + * Creates covers and books folders, which contain book's text and cover. + */ override suspend fun insertBook( - book: Book, - coverImage: CoverImage?, - text: List + bookWithTextAndCover: BookWithTextAndCover ): Boolean { + Log.i(INSERT_BOOK, "Inserting ${bookWithTextAndCover.book.title}.") + val filesDir = application.filesDir val coversDir = File(filesDir, "covers") val booksDir = File(filesDir, "books") if (!coversDir.exists()) { + Log.i(INSERT_BOOK, "Created covers folder.") coversDir.mkdirs() } if (!booksDir.exists()) { + Log.i(INSERT_BOOK, "Created books folder.") booksDir.mkdirs() } var coverUri = "" val textUri: String - if (text.isEmpty()) { + if (bookWithTextAndCover.text.isEmpty()) { + Log.e(INSERT_BOOK, "Text is empty.") return false } @@ -157,18 +181,19 @@ class BookRepositoryImpl @Inject constructor( withContext(Dispatchers.IO) { FileOutputStream(textPath).use { stream -> - text.forEach { line -> + bookWithTextAndCover.text.forEach { line -> stream.write(line.toByteArray()) stream.write(System.lineSeparator().toByteArray()) } } } } catch (e: Exception) { + Log.e(INSERT_BOOK, "Could not write text.") e.printStackTrace() return false } - if (coverImage != null) { + if (bookWithTextAndCover.coverImage != null) { try { coverUri = "${UUID.randomUUID()}.webp" val cover = File(coversDir, coverUri) @@ -176,23 +201,25 @@ class BookRepositoryImpl @Inject constructor( withContext(Dispatchers.IO) { FileOutputStream(cover).use { stream -> if ( - !coverImage.copy(Bitmap.Config.RGB_565, false).compress( - Bitmap.CompressFormat.WEBP, - 20, - stream - ) + !bookWithTextAndCover.coverImage.copy(Bitmap.Config.RGB_565, false) + .compress( + Bitmap.CompressFormat.WEBP, + 20, + stream + ) ) { throw Exception("Couldn't save cover image") } } } } catch (e: Exception) { + Log.e(INSERT_BOOK, "Could not save cover.") coverUri = "" e.printStackTrace() } } - val updatedBook = book.copy( + val updatedBook = bookWithTextAndCover.book.copy( textPath = "$booksDir/$textUri", coverImage = if (coverUri.isNotBlank()) { Uri.fromFile(File("$coversDir/$coverUri")) @@ -201,37 +228,48 @@ class BookRepositoryImpl @Inject constructor( val bookToInsert = bookMapper.toBookEntity(updatedBook) database.insertBooks(listOf(bookToInsert)) + Log.i(INSERT_BOOK, "Successfully inserted book.") return true } - override suspend fun updateBooks(books: List) { + /** + * Update book without text or cover image. + */ + override suspend fun updateBook(book: Book) { // without text and cover image + val entity = database.findBookById(book.id) database.updateBooks( - books.map { - val book = database.findBookById(it.id) + listOf( bookMapper.toBookEntity( - it.copy( - textPath = book.textPath, - coverImage = if (book.image != null) Uri.parse(book.image) else null + book.copy( + textPath = entity.textPath, + coverImage = if (entity.image != null) Uri.parse(entity.image) else null ) ) - } + ) ) } - override suspend fun updateBookWithText(book: Book, text: List): Boolean { + /** + * Update book with text. Deletes old text file and replaces it with new. + */ + override suspend fun updateBookWithText(bookWithText: BookWithText): Boolean { + Log.i(UPDATE_BOOK, "Updating book with text: ${bookWithText.book.title}.") + // without cover image val filesDir = application.filesDir val booksDir = File(filesDir, "books") if (!booksDir.exists()) { + Log.i(UPDATE_BOOK, "Created books folder.") booksDir.mkdirs() } val textUri: String - val bookEntity = database.findBookById(book.id) + val bookEntity = database.findBookById(bookWithText.book.id) - if (text.isEmpty()) { + if (bookWithText.text.isEmpty()) { + Log.e(UPDATE_BOOK, "Text is empty.") return false } @@ -241,33 +279,35 @@ class BookRepositoryImpl @Inject constructor( withContext(Dispatchers.IO) { FileOutputStream(textPath).use { stream -> - text.forEach { line -> + bookWithText.text.forEach { line -> stream.write(line.toByteArray()) stream.write(System.lineSeparator().toByteArray()) } } } } catch (e: Exception) { + Log.e(UPDATE_BOOK, "Could not update text.") e.printStackTrace() return false } - if (book.textPath.isNotBlank()) { + if (bookWithText.book.textPath.isNotBlank()) { try { val fileToDelete = File( - book.textPath + bookWithText.book.textPath ) if (fileToDelete.exists()) { fileToDelete.delete() } } catch (e: Exception) { + Log.e(UPDATE_BOOK, "Failed to delete old text.") e.printStackTrace() } } val updatedBook = bookMapper.toBookEntity( - book.copy( + bookWithText.book.copy( textPath = "$booksDir/$textUri", coverImage = if (bookEntity.image != null) Uri.parse(bookEntity.image) else null ) @@ -276,13 +316,19 @@ class BookRepositoryImpl @Inject constructor( database.updateBooks( listOf(updatedBook) ) + Log.i(UPDATE_BOOK, "Successfully updated book.") return true } + /** + * Update cover image of the book. Deletes old cover and replaces with new. + */ override suspend fun updateCoverImageOfBook( bookWithOldCover: Book, newCoverImage: CoverImage? ) { + Log.i(UPDATE_BOOK, "Updating cover image: ${bookWithOldCover.title}.") + // without text val book = database.findBookById(bookWithOldCover.id) var uri: String? = null @@ -291,6 +337,7 @@ class BookRepositoryImpl @Inject constructor( val coversDir = File(filesDir, "covers") if (!coversDir.exists()) { + Log.i(UPDATE_BOOK, "Created covers folder.") coversDir.mkdirs() } @@ -313,6 +360,7 @@ class BookRepositoryImpl @Inject constructor( } } } catch (e: Exception) { + Log.e(UPDATE_BOOK, "Could not save new cover.") e.printStackTrace() return } @@ -328,6 +376,7 @@ class BookRepositoryImpl @Inject constructor( fileToDelete.delete() } } catch (e: Exception) { + Log.e(UPDATE_BOOK, "Could not delete old cover.") e.printStackTrace() } } @@ -350,17 +399,26 @@ class BookRepositoryImpl @Inject constructor( ) ) ) + Log.i(UPDATE_BOOK, "Successfully updated cover image.") } + /** + * Delete books. + * Also deletes cover image and text from internal storage. + */ override suspend fun deleteBooks(books: List) { + Log.i(DELETE_BOOKS, "Deleting books.") + val filesDir = application.filesDir val coversDir = File(filesDir, "covers") val booksDir = File(filesDir, "books") if (!coversDir.exists()) { + Log.i(DELETE_BOOKS, "Created covers folder.") coversDir.mkdirs() } if (!booksDir.exists()) { + Log.i(DELETE_BOOKS, "Created books folder.") booksDir.mkdirs() } @@ -378,6 +436,7 @@ class BookRepositoryImpl @Inject constructor( fileToDelete.delete() } } catch (e: Exception) { + Log.e(DELETE_BOOKS, "Could not delete cover image.") e.printStackTrace() } } @@ -390,6 +449,7 @@ class BookRepositoryImpl @Inject constructor( fileToDelete.delete() } } catch (e: Exception) { + Log.e(DELETE_BOOKS, "Could not delete text.") e.printStackTrace() } } @@ -397,15 +457,21 @@ class BookRepositoryImpl @Inject constructor( book } ) + + Log.i(DELETE_BOOKS, "Successfully deleted books.") } + /** + * @return Whether can reset cover image (restore default). + */ override suspend fun canResetCover(bookId: Int): Boolean { val book = database.findBookById(bookId) - val defaultCoverUncompressed = fileParser.parse(File(book.filePath))?.second + val defaultCoverUncompressed = fileParser.parse(File(book.filePath))?.coverImage ?: return false if (book.image == null) { + Log.i(CAN_RESET_COVER, "Can reset cover image. (current is null)") return true } @@ -424,6 +490,7 @@ class BookRepositoryImpl @Inject constructor( Uri.parse(book.image) ) } catch (e: Exception) { + Log.i(CAN_RESET_COVER, "Can reset cover image. (could not get current)") e.printStackTrace() return true } @@ -431,30 +498,45 @@ class BookRepositoryImpl @Inject constructor( return !defaultCover.sameAs(currentCover) } + /** + * Reset cover image to default. + * If there is no default cover, returns false. + */ override suspend fun resetCoverImage(bookId: Int): Boolean { if (!canResetCover(bookId)) { + Log.w(RESET_COVER, "Cannot reset cover image.") return false } val book = database.findBookById(bookId) - val defaultCover = fileParser.parse(File(book.filePath))?.second + val defaultCover = fileParser.parse(File(book.filePath))?.coverImage ?: return false updateCoverImageOfBook(bookMapper.toBook(book), defaultCover) + Log.i(RESET_COVER, "Successfully reset cover image.") return true } + /** + * Puts DataStore constant to [DataStore]. + */ override suspend fun putDataToDataStore(key: Preferences.Key, value: T) { dataStore.putData(key, value) } - override suspend fun getAllSettings(scope: CoroutineScope): MainState { + /** + * Gets all settings from DataStore and returns [MainState]. + */ + override suspend fun getAllSettings(): MainState { + Log.i(GET_ALL_SETTINGS, "Getting all settings.") val result = CompletableDeferred() - scope.launch { + withContext(Dispatchers.Default) { val keys = dataStore.getAllData() val data = mutableMapOf() + Log.i(GET_ALL_SETTINGS, "Got ${keys?.size} settings keys.") + val jobs = keys?.map { key -> async { val nullableData = dataStore.getNullableData(key) @@ -474,7 +556,13 @@ class BookRepositoryImpl @Inject constructor( return result.await() } + /** + * Get all matching files from device. + * Filters by [query] and sorts out not supported file formats and already added files. + */ override suspend fun getFilesFromDevice(query: String): List { + Log.i(GET_FILES_FROM_DEVICE, "Getting files from device by query: \"$query\".") + val existingBooks = database .searchBooks("") .map { bookMapper.toBook(it) } @@ -564,9 +652,11 @@ class BookRepositoryImpl @Inject constructor( (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED && Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED_READ_ONLY) ) { + Log.e(GET_FILES_FROM_DEVICE, "Could not correctly get root directory.") return emptyList() } + Log.i(GET_FILES_FROM_DEVICE, "Successfully got all matching files.") return rootDirectory.getAllFiles() } @@ -576,74 +666,106 @@ class BookRepositoryImpl @Inject constructor( override suspend fun getBookFromFile(file: File): NullableBook { val parsedBook = fileParser.parse(file) if (parsedBook == null) { - Log.w(GET_BOOK_FROM_FILE, "Parsed book(${file.name}) is null.") + Log.e(GET_BOOK_FROM_FILE, "Parsed file(${file.name}) is null.") return NullableBook.Null( file.name, - UIText.StringResource(R.string.error_wrong_file_format) + UIText.StringResource(R.string.error_something_went_wrong) ) } val parsedText = textParser.parse(file) if (parsedText is Resource.Error) { - Log.w(GET_BOOK_FROM_FILE, "Parsed text(${file.name}) has error.") + Log.e(GET_BOOK_FROM_FILE, "Parsed text(${file.name}) has error.") return NullableBook.Null( file.name, parsedText.message ) } + Log.i(GET_BOOK_FROM_FILE, "Successfully got book from file.") return NullableBook.NotNull( - book = parsedBook.first.copy( - chapters = parsedText.data!!.map { it.chapter }.run { - if (this.size == 1) return@run emptyList() - this - } - ), - coverImage = parsedBook.second, - text = parsedText.data.map { - it.text - }.flatten() + bookWithTextAndCover = BookWithTextAndCover( + book = parsedBook.book.copy( + chapters = parsedText.data!!.map { it.chapter }.run { + if (this.size == 1) return@run emptyList() + this + } + ), + coverImage = parsedBook.coverImage, + text = parsedText.data.map { + it.text + }.flatten() + ) ) } - override suspend fun insertHistory(history: List) { - database.insertHistory(history.map { historyMapper.toHistoryEntity(it) }) + /** + * Insert history in database. + */ + override suspend fun insertHistory(history: History) { + database.insertHistory( + listOf( + historyMapper.toHistoryEntity( + history + ) + ) + ) } - override suspend fun getHistory(): Flow>> { - return flow { - val history = database.getHistory() - - emit( - Resource.Success( - data = history.map { - historyMapper.toHistory( - it - ) - } - ) + /** + * Get all history from database. + */ + override suspend fun getHistory(): List { + return database.getHistory().map { + historyMapper.toHistory( + it ) } } + /** + * Get latest history of the matching [bookId]. + */ override suspend fun getLatestBookHistory(bookId: Int): History? { val history = database.getLatestHistoryForBook(bookId) return history?.let { historyMapper.toHistory(it) } } + /** + * Delete whole history. + */ override suspend fun deleteWholeHistory() { database.deleteWholeHistory() } + /** + * Delete all history of the matching [bookId]. + */ override suspend fun deleteBookHistory(bookId: Int) { database.deleteBookHistory(bookId) } - override suspend fun deleteHistory(history: List) { - database.deleteHistory(history.map { historyMapper.toHistoryEntity(it) }) + /** + * Delete specific history item. + */ + override suspend fun deleteHistory(history: History) { + database.deleteHistory( + listOf( + historyMapper.toHistoryEntity( + history + ) + ) + ) } + /** + * Check for updates from GitHub. + * + * @param postNotification Whether notification should be send. + */ override suspend fun checkForUpdates(postNotification: Boolean): LatestReleaseInfo? { + Log.i(CHECK_FOR_UPDATES, "Checking for updates. Post notification: $postNotification") + return withContext(Dispatchers.IO) { try { val result = githubAPI.getLatestRelease() @@ -652,6 +774,7 @@ class BookRepositoryImpl @Inject constructor( val currentVersion = application.getString(R.string.app_version) if (version != currentVersion && postNotification) { + Log.i(CHECK_FOR_UPDATES, "Posting notification.") updatesNotificationService.postNotification( result ) @@ -659,12 +782,16 @@ class BookRepositoryImpl @Inject constructor( result } catch (e: Exception) { + Log.e(CHECK_FOR_UPDATES, "Could not get latest release information.") e.printStackTrace() null } } } + /** + * Update color preset. + */ override suspend fun updateColorPreset(colorPreset: ColorPreset) { database.updateColorPreset( colorPresetMapper.toColorPresetEntity( @@ -675,6 +802,9 @@ class BookRepositoryImpl @Inject constructor( ) } + /** + * Select color preset. Only one can be selected at time. + */ override suspend fun selectColorPreset(colorPreset: ColorPreset) { database.getColorPresets().map { it.copy( @@ -685,12 +815,20 @@ class BookRepositoryImpl @Inject constructor( } } + /** + * Get all color presets. + * Sorted by order (either manual or newest ones at the end). + */ override suspend fun getColorPresets(): List { return database.getColorPresets() .sortedBy { it.order } .map { colorPresetMapper.toColorPreset(it) } } + /** + * Reorder color presets. + * Changes the order of the color presets. + */ override suspend fun reorderColorPresets(orderedColorPresets: List) { database.deleteColorPresets() @@ -701,6 +839,9 @@ class BookRepositoryImpl @Inject constructor( } } + /** + * Delete color preset. + */ override suspend fun deleteColorPreset(colorPreset: ColorPreset) { database.deleteColorPreset( colorPresetMapper.toColorPresetEntity( @@ -709,6 +850,11 @@ class BookRepositoryImpl @Inject constructor( ) } + /** + * Create or delete favorite directory if already exists. + * + * @param path Path to directory. + */ override suspend fun updateFavoriteDirectory(path: String) { if (database.favoriteDirectoryExits(path)) { database.deleteFavoriteDirectory( @@ -721,19 +867,4 @@ class BookRepositoryImpl @Inject constructor( FavoriteDirectoryEntity(path) ) } -} - - - - - - - - - - - - - - - +} \ No newline at end of file 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 b20438a8..99d1e240 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 @@ -2,6 +2,7 @@ package ua.acclorite.book_story.domain.model import android.net.Uri import androidx.compose.runtime.Immutable +import ua.acclorite.book_story.domain.util.CoverImage import ua.acclorite.book_story.domain.util.UIText @Immutable @@ -23,4 +24,47 @@ data class Book( val lastOpened: Long?, val category: Category, -) \ No newline at end of file +) + +@Immutable +data class BookWithText( + val book: Book, + val text: List +) + +@Immutable +data class BookWithCover( + val book: Book, + val coverImage: CoverImage? +) + +@Immutable +data class BookWithTextAndCover( + val book: Book, + val coverImage: CoverImage?, + val text: List +) + +@Immutable +sealed class NullableBook( + val bookWithTextAndCover: BookWithTextAndCover?, + val fileName: String?, + val message: UIText? +) { + class NotNull( + bookWithTextAndCover: BookWithTextAndCover + ) : NullableBook( + bookWithTextAndCover = bookWithTextAndCover, + fileName = null, + message = null + ) + + class Null( + fileName: String, + message: UIText? + ) : NullableBook( + bookWithTextAndCover = null, + fileName = fileName, + message = message + ) +} \ No newline at end of file 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 deleted file mode 100644 index 0d294faf..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/model/NullableBook.kt +++ /dev/null @@ -1,31 +0,0 @@ -package ua.acclorite.book_story.domain.model - -import androidx.compose.runtime.Immutable -import ua.acclorite.book_story.domain.util.CoverImage -import ua.acclorite.book_story.domain.util.UIText - -@Immutable -sealed class NullableBook( - val book: Book?, - val coverImage: CoverImage? = null, - val text: List = emptyList(), - val fileName: String?, - val message: UIText? -) { - 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, 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 fb1d8582..1f799b81 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 @@ -1,21 +1,21 @@ package ua.acclorite.book_story.domain.repository import androidx.datastore.preferences.core.Preferences -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithText +import ua.acclorite.book_story.domain.model.BookWithTextAndCover import ua.acclorite.book_story.domain.model.ColorPreset import ua.acclorite.book_story.domain.model.History import ua.acclorite.book_story.domain.model.NullableBook import ua.acclorite.book_story.domain.model.SelectableFile import ua.acclorite.book_story.domain.util.CoverImage -import ua.acclorite.book_story.domain.util.Resource import ua.acclorite.book_story.presentation.data.MainState import java.io.File interface BookRepository { + /* ------ Books ------------------------------ */ suspend fun getBooks( query: String ): List @@ -29,18 +29,15 @@ interface BookRepository { ): List suspend fun insertBook( - book: Book, - coverImage: CoverImage?, - text: List + bookWithTextAndCover: BookWithTextAndCover ): Boolean - suspend fun updateBooks( - books: List + suspend fun updateBook( + book: Book ) suspend fun updateBookWithText( - book: Book, - text: List + bookWithText: BookWithText ): Boolean suspend fun updateCoverImageOfBook( @@ -56,49 +53,85 @@ interface BookRepository { bookId: Int ): Boolean - suspend fun resetCoverImage(bookId: Int): Boolean + suspend fun resetCoverImage( + bookId: Int + ): Boolean + /* - - - - - - - - - - - - - - - - - - - - - - */ + + /* ------ DataStore -------------------------- */ suspend fun putDataToDataStore( key: Preferences.Key, value: T ) - - suspend fun getAllSettings(scope: CoroutineScope): MainState - - suspend fun getFilesFromDevice(query: String = ""): List - - suspend fun getBookFromFile(file: File): NullableBook + suspend fun getAllSettings(): MainState + /* - - - - - - - - - - - - - - - - - - - - - - */ - suspend fun insertHistory(history: List) + /* ------ File System ------------------------ */ + suspend fun getFilesFromDevice( + query: String = "" + ): List - suspend fun getHistory(): Flow>> + suspend fun getBookFromFile( + file: File + ): NullableBook + /* - - - - - - - - - - - - - - - - - - - - - - */ - suspend fun getLatestBookHistory(bookId: Int): History? + + /* ------ History ---------------------------- */ + suspend fun insertHistory( + history: History + ) + + suspend fun getHistory(): List + + suspend fun getLatestBookHistory( + bookId: Int + ): History? suspend fun deleteWholeHistory() - suspend fun deleteBookHistory(bookId: Int) - - suspend fun deleteHistory( - history: List + suspend fun deleteBookHistory( + bookId: Int ) - - suspend fun checkForUpdates(postNotification: Boolean): LatestReleaseInfo? + suspend fun deleteHistory( + history: History + ) + /* - - - - - - - - - - - - - - - - - - - - - - */ - suspend fun updateColorPreset(colorPreset: ColorPreset) + /* ------ API (GitHub) ----------------------- */ + suspend fun checkForUpdates( + postNotification: Boolean + ): LatestReleaseInfo? + /* - - - - - - - - - - - - - - - - - - - - - - */ - suspend fun selectColorPreset(colorPreset: ColorPreset) + + /* ------ Color Presets ---------------------- */ + suspend fun updateColorPreset( + colorPreset: ColorPreset + ) + + suspend fun selectColorPreset( + colorPreset: ColorPreset + ) suspend fun getColorPresets(): List - suspend fun reorderColorPresets(orderedColorPresets: List) + suspend fun reorderColorPresets( + orderedColorPresets: List + ) - suspend fun deleteColorPreset(colorPreset: ColorPreset) + suspend fun deleteColorPreset( + colorPreset: ColorPreset + ) + /* - - - - - - - - - - - - - - - - - - - - - - */ + /* ------ Favorite Directories --------------- */ suspend fun updateFavoriteDirectory(path: String) + /* - - - - - - - - - - - - - - - - - - - - - - */ } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/DeleteHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/DeleteHistory.kt index 90ad74ed..e9f53973 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/DeleteHistory.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/DeleteHistory.kt @@ -6,7 +6,7 @@ import javax.inject.Inject class DeleteHistory @Inject constructor(private val repository: BookRepository) { - suspend fun execute(history: List) { + suspend fun execute(history: History) { repository.deleteHistory(history) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetAllSettings.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetAllSettings.kt index ab8a124e..9d1c7b69 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetAllSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetAllSettings.kt @@ -1,6 +1,5 @@ package ua.acclorite.book_story.domain.use_case -import kotlinx.coroutines.CoroutineScope import ua.acclorite.book_story.domain.repository.BookRepository import ua.acclorite.book_story.presentation.data.MainState import javax.inject.Inject @@ -9,7 +8,7 @@ class GetAllSettings @Inject constructor( private val repository: BookRepository ) { - suspend fun execute(scope: CoroutineScope): MainState { - return repository.getAllSettings(scope) + suspend fun execute(): MainState { + return repository.getAllSettings() } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetHistory.kt index bf7b1efc..d55edca1 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetHistory.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetHistory.kt @@ -1,14 +1,12 @@ package ua.acclorite.book_story.domain.use_case -import kotlinx.coroutines.flow.Flow import ua.acclorite.book_story.domain.model.History import ua.acclorite.book_story.domain.repository.BookRepository -import ua.acclorite.book_story.domain.util.Resource import javax.inject.Inject class GetHistory @Inject constructor(private val repository: BookRepository) { - suspend fun execute(): Flow>> { + suspend fun execute(): List { return repository.getHistory() } } \ 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 index 679443e4..b0013a89 100644 --- 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 @@ -1,16 +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.BookWithTextAndCover 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 + bookWithTextAndCover: BookWithTextAndCover ): Boolean { - return repository.insertBook(book, coverImage, text) + return repository.insertBook(bookWithTextAndCover) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertHistory.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertHistory.kt index 45a168ca..57e2c270 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertHistory.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/InsertHistory.kt @@ -5,7 +5,7 @@ import ua.acclorite.book_story.domain.repository.BookRepository import javax.inject.Inject class InsertHistory @Inject constructor(private val repository: BookRepository) { - suspend fun execute(history: List) { + suspend fun execute(history: History) { repository.insertHistory(history) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBook.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBook.kt index 9bb8fd29..5a32ecd0 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBook.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/UpdateBook.kt @@ -7,6 +7,6 @@ import javax.inject.Inject class UpdateBook @Inject constructor(private val repository: BookRepository) { suspend fun execute(book: Book) { - repository.updateBooks(listOf(book)) + repository.updateBook(book) } } \ 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 index 5640f275..4ccab040 100644 --- 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 @@ -1,12 +1,12 @@ package ua.acclorite.book_story.domain.use_case -import ua.acclorite.book_story.domain.model.Book +import ua.acclorite.book_story.domain.model.BookWithText 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) + suspend fun execute(bookWithText: BookWithText): Boolean { + return repository.updateBookWithText(bookWithText) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt index 4e8295fa..b474e74c 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/MainViewModel.kt @@ -468,7 +468,7 @@ class MainViewModel @Inject constructor( settingsViewModel: SettingsViewModel, ) { viewModelScope.launch(Dispatchers.Main) { - val settings = getAllSettings.execute(viewModelScope) + val settings = getAllSettings.execute() // All additional execution changeLanguage.execute(settings.language) 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 15021161..ef26db4d 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 @@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.yield 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 @@ -531,7 +532,7 @@ class BookInfoViewModel @Inject constructor( var textUpdated = false - val updatedText = updatedBook.text + val updatedText = updatedBook.bookWithTextAndCover!!.text val text = getText.execute(book.textPath) if (updatedText != text) { @@ -560,7 +561,7 @@ class BookInfoViewModel @Inject constructor( yield() onEvent( BookInfoEvent.OnShowConfirmUpdateDialog( - updatedText = updatedBook.text, + updatedText = updatedBook.bookWithTextAndCover.text, ) ) @@ -629,8 +630,10 @@ class BookInfoViewModel @Inject constructor( val updatedText = _state.value.updatedText ?: return@launch val isSuccess = updateBookWithText.execute( - book = _state.value.book, - text = updatedText + BookWithText( + book = _state.value.book, + text = updatedText + ) ) if (!isSuccess) { @@ -732,12 +735,10 @@ class BookInfoViewModel @Inject constructor( onEvent(BookInfoEvent.OnCancelUpdate) _state.value.book.id.let { insertHistory.execute( - listOf( - History( - bookId = it, - book = null, - time = Date().time - ) + History( + bookId = it, + book = null, + time = Date().time ) ) } 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 c56c5b0a..098ed6fd 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 @@ -46,14 +46,14 @@ fun BrowseAddingDialogItem(result: Pair, onClick: (Boole Modifier.weight(1f) ) { Text( - text = result.first.book!!.title, + text = result.first.bookWithTextAndCover!!.book.title, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface, maxLines = 1, overflow = TextOverflow.Ellipsis ) Text( - text = result.first.book!!.author.asString(), + text = result.first.bookWithTextAndCover!!.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 3ef8cce9..31c9f58d 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 @@ -467,12 +467,11 @@ class BrowseViewModel @Inject constructor( return@launch } - val failed = booksToInsert.any { - !insertBook.execute( - it.book!!, - it.coverImage, - it.text - ) + var failed = false + booksToInsert.forEach { + if (!insertBook.execute(it.bookWithTextAndCover!!)) { + failed = true + } } if (failed) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt index dcb6877f..01dc7739 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/history/data/HistoryViewModel.kt @@ -20,7 +20,6 @@ import ua.acclorite.book_story.domain.use_case.DeleteWholeHistory import ua.acclorite.book_story.domain.use_case.GetBooksById import ua.acclorite.book_story.domain.use_case.GetHistory import ua.acclorite.book_story.domain.use_case.InsertHistory -import ua.acclorite.book_story.domain.util.Resource import ua.acclorite.book_story.presentation.core.navigation.Screen import ua.acclorite.book_story.presentation.core.util.BaseViewModel import java.text.SimpleDateFormat @@ -119,9 +118,7 @@ class HistoryViewModel @Inject constructor( is HistoryEvent.OnDeleteHistoryElement -> { viewModelScope.launch(Dispatchers.IO) { - deleteHistory.execute( - listOf(event.historyToDelete) - ) + deleteHistory.execute(event.historyToDelete) _state.update { it.copy( @@ -154,9 +151,7 @@ class HistoryViewModel @Inject constructor( when (snackbar) { SnackbarResult.Dismissed -> Unit SnackbarResult.ActionPerformed -> { - insertHistory.execute( - listOf(event.historyToDelete) - ) + insertHistory.execute(event.historyToDelete) event.refreshList() _state.update { it.copy( @@ -256,12 +251,10 @@ class HistoryViewModel @Inject constructor( viewModelScope.launch { event.book.id.let { insertHistory.execute( - listOf( - History( - bookId = it, - book = null, - time = Date().time - ) + History( + bookId = it, + book = null, + time = Date().time ) ) } @@ -300,91 +293,82 @@ class HistoryViewModel @Inject constructor( return maxElementsById.filterNotNull() } - getHistory.execute().collect { result -> - when (result) { - is Resource.Success -> { - val historyWithoutBook = - result.data?.sortedByDescending { it.time } ?: emptyList() + val historyWithoutBooks = getHistory.execute() + .sortedByDescending { it.time } - if (historyWithoutBook.isEmpty()) { - _state.update { - it.copy( - history = emptyList(), - isLoading = false - ) - } - return@collect - } - - val books = getBooksById.execute( - historyWithoutBook.map { it.bookId }.distinct() - ) - - if (books.isEmpty()) { - _state.update { - it.copy( - history = emptyList(), - isLoading = false - ) - } - return@collect - } - - val history = historyWithoutBook.map { - val book = books.find { book -> book.id == it.bookId }!! - it.copy( - book = book - ) - } - - val groupedHistory = mutableListOf() - - history - .filter { - val book = books.find { book -> book.id == it.bookId }!! - book.title.lowercase().trim().contains(query.lowercase().trim()) - }.groupBy { item -> - val calendar = Calendar.getInstance().apply { - timeInMillis = item.time - } - val now = Calendar.getInstance() - - when { - isSameDay(calendar, now) -> "today" - isSameDay( - calendar, - now.apply { - add( - Calendar.DAY_OF_YEAR, - -1 - ) - }) -> "yesterday" - - else -> SimpleDateFormat( - "dd.MM.yy", - Locale.getDefault() - ).format(item.time) - } - }.forEach { (key, value) -> - groupedHistory.add( - GroupedHistory( - key, - filterMaxElementsById(value) - ) - ) - } - - _state.update { - it.copy( - history = groupedHistory, - isLoading = false - ) - } - } - - is Resource.Error -> Unit + if (historyWithoutBooks.isEmpty()) { + _state.update { + it.copy( + history = emptyList(), + isLoading = false + ) } + return + } + + val books = getBooksById.execute( + historyWithoutBooks.map { it.bookId }.distinct() + ) + + if (books.isEmpty()) { + _state.update { + it.copy( + history = emptyList(), + isLoading = false + ) + } + return + } + + val history = historyWithoutBooks.map { + val book = books.find { book -> book.id == it.bookId }!! + it.copy( + book = book + ) + } + + val groupedHistory = mutableListOf() + + history + .filter { + val book = books.find { book -> book.id == it.bookId }!! + book.title.lowercase().trim().contains(query.lowercase().trim()) + }.groupBy { item -> + val calendar = Calendar.getInstance().apply { + timeInMillis = item.time + } + val now = Calendar.getInstance() + + when { + isSameDay(calendar, now) -> "today" + isSameDay( + calendar, + now.apply { + add( + Calendar.DAY_OF_YEAR, + -1 + ) + }) -> "yesterday" + + else -> SimpleDateFormat( + "dd.MM.yy", + Locale.getDefault() + ).format(item.time) + } + }.forEach { (key, value) -> + groupedHistory.add( + GroupedHistory( + key, + filterMaxElementsById(value) + ) + ) + } + + _state.update { + it.copy( + history = groupedHistory, + isLoading = false + ) } } -} - +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt index e3652faa..04124ce9 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/library/data/LibraryViewModel.kt @@ -328,12 +328,10 @@ class LibraryViewModel @Inject constructor( viewModelScope.launch { event.book.id.let { insertHistory.execute( - listOf( - History( - bookId = it, - book = null, - time = Date().time - ) + History( + bookId = it, + book = null, + time = Date().time ) ) }