🛠️ Add logging and reformat data layer
* Reformatted BookRepository, added logs * Improved code readability, added comments
This commit is contained in:
parent
94c49c20c8
commit
100926ce14
25 changed files with 544 additions and 375 deletions
|
|
@ -1,11 +1,10 @@
|
||||||
package ua.acclorite.book_story.data.parser
|
package ua.acclorite.book_story.data.parser
|
||||||
|
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.BookWithCover
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
interface FileParser {
|
interface FileParser {
|
||||||
|
|
||||||
suspend fun parse(file: File): Pair<Book, CoverImage?>?
|
suspend fun parse(file: File): BookWithCover?
|
||||||
}
|
}
|
||||||
|
|
@ -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.html.HtmlFileParser
|
||||||
import ua.acclorite.book_story.data.parser.pdf.PdfFileParser
|
import ua.acclorite.book_story.data.parser.pdf.PdfFileParser
|
||||||
import ua.acclorite.book_story.data.parser.txt.TxtFileParser
|
import ua.acclorite.book_story.data.parser.txt.TxtFileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
import ua.acclorite.book_story.domain.model.BookWithCover
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -22,7 +21,7 @@ class FileParserImpl @Inject constructor(
|
||||||
private val htmlFileParser: HtmlFileParser,
|
private val htmlFileParser: HtmlFileParser,
|
||||||
private val htmFileParser: HtmFileParser,
|
private val htmFileParser: HtmFileParser,
|
||||||
) : FileParser {
|
) : FileParser {
|
||||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
override suspend fun parse(file: File): BookWithCover? {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
Log.e(FILE_PARSER, "File does not exist.")
|
Log.e(FILE_PARSER, "File does not exist.")
|
||||||
return null
|
return null
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import org.jsoup.Jsoup
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
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.model.Category
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
|
|
@ -17,9 +17,9 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class EpubFileParser @Inject constructor() : FileParser {
|
class EpubFileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
override suspend fun parse(file: File): BookWithCover? {
|
||||||
return try {
|
return try {
|
||||||
var book: Pair<Book, CoverImage?>? = null
|
var book: BookWithCover? = null
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
ZipFile(file).use { zip ->
|
ZipFile(file).use { zip ->
|
||||||
|
|
@ -71,19 +71,22 @@ class EpubFileParser @Inject constructor() : FileParser {
|
||||||
.firstOrNull()?.attr("href")
|
.firstOrNull()?.attr("href")
|
||||||
}
|
}
|
||||||
|
|
||||||
book = Book(
|
book = BookWithCover(
|
||||||
title = title,
|
book = Book(
|
||||||
author = author,
|
title = title,
|
||||||
description = description,
|
author = author,
|
||||||
textPath = "",
|
description = description,
|
||||||
scrollIndex = 0,
|
textPath = "",
|
||||||
scrollOffset = 0,
|
scrollIndex = 0,
|
||||||
progress = 0f,
|
scrollOffset = 0,
|
||||||
filePath = file.path,
|
progress = 0f,
|
||||||
lastOpened = null,
|
filePath = file.path,
|
||||||
category = Category.entries[0],
|
lastOpened = null,
|
||||||
coverImage = null
|
category = Category.entries[0],
|
||||||
) to extractCoverImageBitmap(file, coverImage)
|
coverImage = null
|
||||||
|
),
|
||||||
|
coverImage = extractCoverImageBitmap(file, coverImage)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
book
|
book
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import org.w3c.dom.Element
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
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.model.Category
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -16,7 +16,7 @@ import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
class Fb2FileParser @Inject constructor() : FileParser {
|
class Fb2FileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
override suspend fun parse(file: File): BookWithCover? {
|
||||||
return try {
|
return try {
|
||||||
val factory = DocumentBuilderFactory.newInstance()
|
val factory = DocumentBuilderFactory.newInstance()
|
||||||
val builder = factory.newDocumentBuilder()
|
val builder = factory.newDocumentBuilder()
|
||||||
|
|
@ -50,19 +50,22 @@ class Fb2FileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
val descriptionFromFile = extractElementContent(document, "annotation")
|
val descriptionFromFile = extractElementContent(document, "annotation")
|
||||||
|
|
||||||
Book(
|
BookWithCover(
|
||||||
title = title,
|
book = Book(
|
||||||
author = author,
|
title = title,
|
||||||
description = descriptionFromFile,
|
author = author,
|
||||||
textPath = "",
|
description = descriptionFromFile,
|
||||||
scrollIndex = 0,
|
textPath = "",
|
||||||
scrollOffset = 0,
|
scrollIndex = 0,
|
||||||
progress = 0f,
|
scrollOffset = 0,
|
||||||
filePath = file.path,
|
progress = 0f,
|
||||||
lastOpened = null,
|
filePath = file.path,
|
||||||
category = Category.entries[0],
|
lastOpened = null,
|
||||||
|
category = Category.entries[0],
|
||||||
|
coverImage = null
|
||||||
|
),
|
||||||
coverImage = null
|
coverImage = null
|
||||||
) to null
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@ import org.jsoup.Jsoup
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
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.model.Category
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class HtmFileParser @Inject constructor() : FileParser {
|
class HtmFileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
override suspend fun parse(file: File): BookWithCover? {
|
||||||
return try {
|
return try {
|
||||||
val document = Jsoup.parse(file)
|
val document = Jsoup.parse(file)
|
||||||
|
|
||||||
|
|
@ -22,19 +22,22 @@ class HtmFileParser @Inject constructor() : FileParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Book(
|
BookWithCover(
|
||||||
title = title,
|
book = Book(
|
||||||
author = UIText.StringResource(R.string.unknown_author),
|
title = title,
|
||||||
description = null,
|
author = UIText.StringResource(R.string.unknown_author),
|
||||||
textPath = "",
|
description = null,
|
||||||
scrollIndex = 0,
|
textPath = "",
|
||||||
scrollOffset = 0,
|
scrollIndex = 0,
|
||||||
progress = 0f,
|
scrollOffset = 0,
|
||||||
filePath = file.path,
|
progress = 0f,
|
||||||
lastOpened = null,
|
filePath = file.path,
|
||||||
category = Category.entries[0],
|
lastOpened = null,
|
||||||
|
category = Category.entries[0],
|
||||||
|
coverImage = null
|
||||||
|
),
|
||||||
coverImage = null
|
coverImage = null
|
||||||
) to null
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@ import org.jsoup.Jsoup
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
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.model.Category
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class HtmlFileParser @Inject constructor() : FileParser {
|
class HtmlFileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
override suspend fun parse(file: File): BookWithCover? {
|
||||||
return try {
|
return try {
|
||||||
val document = Jsoup.parse(file)
|
val document = Jsoup.parse(file)
|
||||||
|
|
||||||
|
|
@ -22,19 +22,22 @@ class HtmlFileParser @Inject constructor() : FileParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Book(
|
BookWithCover(
|
||||||
title = title,
|
book = Book(
|
||||||
author = UIText.StringResource(R.string.unknown_author),
|
title = title,
|
||||||
description = null,
|
author = UIText.StringResource(R.string.unknown_author),
|
||||||
textPath = "",
|
description = null,
|
||||||
scrollIndex = 0,
|
textPath = "",
|
||||||
scrollOffset = 0,
|
scrollIndex = 0,
|
||||||
progress = 0f,
|
scrollOffset = 0,
|
||||||
filePath = file.path,
|
progress = 0f,
|
||||||
lastOpened = null,
|
filePath = file.path,
|
||||||
category = Category.entries[0],
|
lastOpened = null,
|
||||||
|
category = Category.entries[0],
|
||||||
|
coverImage = null
|
||||||
|
),
|
||||||
coverImage = null
|
coverImage = null
|
||||||
) to null
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,15 @@ import com.tom_roush.pdfbox.pdmodel.PDDocument
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
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.model.Category
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PdfFileParser @Inject constructor(private val application: Application) : FileParser {
|
class PdfFileParser @Inject constructor(private val application: Application) : FileParser {
|
||||||
|
|
||||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
override suspend fun parse(file: File): BookWithCover? {
|
||||||
return try {
|
return try {
|
||||||
PDFBoxResourceLoader.init(application)
|
PDFBoxResourceLoader.init(application)
|
||||||
|
|
||||||
|
|
@ -29,19 +29,22 @@ class PdfFileParser @Inject constructor(private val application: Application) :
|
||||||
|
|
||||||
document.close()
|
document.close()
|
||||||
|
|
||||||
Book(
|
BookWithCover(
|
||||||
title = title,
|
book = Book(
|
||||||
author = author,
|
title = title,
|
||||||
description = description,
|
author = author,
|
||||||
textPath = "",
|
description = description,
|
||||||
scrollIndex = 0,
|
textPath = "",
|
||||||
scrollOffset = 0,
|
scrollIndex = 0,
|
||||||
progress = 0f,
|
scrollOffset = 0,
|
||||||
filePath = file.path,
|
progress = 0f,
|
||||||
lastOpened = null,
|
filePath = file.path,
|
||||||
category = Category.entries[0],
|
lastOpened = null,
|
||||||
|
category = Category.entries[0],
|
||||||
|
coverImage = null
|
||||||
|
),
|
||||||
coverImage = null
|
coverImage = null
|
||||||
) to null
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -3,32 +3,35 @@ package ua.acclorite.book_story.data.parser.txt
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
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.model.Category
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TxtFileParser @Inject constructor() : FileParser {
|
class TxtFileParser @Inject constructor() : FileParser {
|
||||||
|
|
||||||
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
|
override suspend fun parse(file: File): BookWithCover? {
|
||||||
return try {
|
return try {
|
||||||
val title = file.nameWithoutExtension.trim()
|
val title = file.nameWithoutExtension.trim()
|
||||||
val author = UIText.StringResource(R.string.unknown_author)
|
val author = UIText.StringResource(R.string.unknown_author)
|
||||||
|
|
||||||
Book(
|
BookWithCover(
|
||||||
title = title,
|
book = Book(
|
||||||
author = author,
|
title = title,
|
||||||
description = null,
|
author = author,
|
||||||
textPath = "",
|
description = null,
|
||||||
scrollIndex = 0,
|
textPath = "",
|
||||||
scrollOffset = 0,
|
scrollIndex = 0,
|
||||||
progress = 0f,
|
scrollOffset = 0,
|
||||||
filePath = file.path,
|
progress = 0f,
|
||||||
lastOpened = null,
|
filePath = file.path,
|
||||||
category = Category.entries[0],
|
lastOpened = null,
|
||||||
|
category = Category.entries[0],
|
||||||
|
coverImage = null
|
||||||
|
),
|
||||||
coverImage = null
|
coverImage = null
|
||||||
) to null
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,9 @@ import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.data.local.data_store.DataStore
|
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.GithubAPI
|
||||||
import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo
|
import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
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.ColorPreset
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.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_BOOK_FROM_FILE = "GET BOOK FROM FILE, REPOSITORY"
|
||||||
private const val GET_TEXT = "GET TEXT, 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")
|
@Suppress("DEPRECATION")
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
@ -67,13 +75,18 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
private val colorPresetMapper: ColorPresetMapper,
|
private val colorPresetMapper: ColorPresetMapper,
|
||||||
|
|
||||||
private val fileParser: FileParser,
|
private val fileParser: FileParser,
|
||||||
private val textParser: TextParser
|
private val textParser: TextParser,
|
||||||
|
|
||||||
) : BookRepository {
|
) : BookRepository {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all books matching [query] from database.
|
||||||
|
* Empty [query] equals to all books.
|
||||||
|
*/
|
||||||
override suspend fun getBooks(query: String): List<Book> {
|
override suspend fun getBooks(query: String): List<Book> {
|
||||||
|
Log.i(GET_BOOKS, "Searching for books with query: \"$query\".")
|
||||||
val books = database.searchBooks(query)
|
val books = database.searchBooks(query)
|
||||||
|
|
||||||
|
Log.i(GET_BOOKS, "Found ${books.size} books.")
|
||||||
return books.map { entity ->
|
return books.map { entity ->
|
||||||
val book = bookMapper.toBook(entity)
|
val book = bookMapper.toBook(entity)
|
||||||
val lastHistory = database.getLatestHistoryForBook(
|
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<Int>): List<Book> {
|
override suspend fun getBooksById(ids: List<Int>): List<Book> {
|
||||||
|
Log.i(GET_BOOKS_BY_ID, "Getting books with ids: $ids.")
|
||||||
val books = database.findBooksById(ids)
|
val books = database.findBooksById(ids)
|
||||||
|
|
||||||
return books.map { entity ->
|
return books.map { entity ->
|
||||||
|
|
@ -128,26 +145,33 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts book in database.
|
||||||
|
* Creates covers and books folders, which contain book's text and cover.
|
||||||
|
*/
|
||||||
override suspend fun insertBook(
|
override suspend fun insertBook(
|
||||||
book: Book,
|
bookWithTextAndCover: BookWithTextAndCover
|
||||||
coverImage: CoverImage?,
|
|
||||||
text: List<String>
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
Log.i(INSERT_BOOK, "Inserting ${bookWithTextAndCover.book.title}.")
|
||||||
|
|
||||||
val filesDir = application.filesDir
|
val filesDir = application.filesDir
|
||||||
val coversDir = File(filesDir, "covers")
|
val coversDir = File(filesDir, "covers")
|
||||||
val booksDir = File(filesDir, "books")
|
val booksDir = File(filesDir, "books")
|
||||||
|
|
||||||
if (!coversDir.exists()) {
|
if (!coversDir.exists()) {
|
||||||
|
Log.i(INSERT_BOOK, "Created covers folder.")
|
||||||
coversDir.mkdirs()
|
coversDir.mkdirs()
|
||||||
}
|
}
|
||||||
if (!booksDir.exists()) {
|
if (!booksDir.exists()) {
|
||||||
|
Log.i(INSERT_BOOK, "Created books folder.")
|
||||||
booksDir.mkdirs()
|
booksDir.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
var coverUri = ""
|
var coverUri = ""
|
||||||
val textUri: String
|
val textUri: String
|
||||||
|
|
||||||
if (text.isEmpty()) {
|
if (bookWithTextAndCover.text.isEmpty()) {
|
||||||
|
Log.e(INSERT_BOOK, "Text is empty.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,18 +181,19 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
FileOutputStream(textPath).use { stream ->
|
FileOutputStream(textPath).use { stream ->
|
||||||
text.forEach { line ->
|
bookWithTextAndCover.text.forEach { line ->
|
||||||
stream.write(line.toByteArray())
|
stream.write(line.toByteArray())
|
||||||
stream.write(System.lineSeparator().toByteArray())
|
stream.write(System.lineSeparator().toByteArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(INSERT_BOOK, "Could not write text.")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coverImage != null) {
|
if (bookWithTextAndCover.coverImage != null) {
|
||||||
try {
|
try {
|
||||||
coverUri = "${UUID.randomUUID()}.webp"
|
coverUri = "${UUID.randomUUID()}.webp"
|
||||||
val cover = File(coversDir, coverUri)
|
val cover = File(coversDir, coverUri)
|
||||||
|
|
@ -176,23 +201,25 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
FileOutputStream(cover).use { stream ->
|
FileOutputStream(cover).use { stream ->
|
||||||
if (
|
if (
|
||||||
!coverImage.copy(Bitmap.Config.RGB_565, false).compress(
|
!bookWithTextAndCover.coverImage.copy(Bitmap.Config.RGB_565, false)
|
||||||
Bitmap.CompressFormat.WEBP,
|
.compress(
|
||||||
20,
|
Bitmap.CompressFormat.WEBP,
|
||||||
stream
|
20,
|
||||||
)
|
stream
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
throw Exception("Couldn't save cover image")
|
throw Exception("Couldn't save cover image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(INSERT_BOOK, "Could not save cover.")
|
||||||
coverUri = ""
|
coverUri = ""
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val updatedBook = book.copy(
|
val updatedBook = bookWithTextAndCover.book.copy(
|
||||||
textPath = "$booksDir/$textUri",
|
textPath = "$booksDir/$textUri",
|
||||||
coverImage = if (coverUri.isNotBlank()) {
|
coverImage = if (coverUri.isNotBlank()) {
|
||||||
Uri.fromFile(File("$coversDir/$coverUri"))
|
Uri.fromFile(File("$coversDir/$coverUri"))
|
||||||
|
|
@ -201,37 +228,48 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
val bookToInsert = bookMapper.toBookEntity(updatedBook)
|
val bookToInsert = bookMapper.toBookEntity(updatedBook)
|
||||||
database.insertBooks(listOf(bookToInsert))
|
database.insertBooks(listOf(bookToInsert))
|
||||||
|
Log.i(INSERT_BOOK, "Successfully inserted book.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateBooks(books: List<Book>) {
|
/**
|
||||||
|
* Update book without text or cover image.
|
||||||
|
*/
|
||||||
|
override suspend fun updateBook(book: Book) {
|
||||||
// without text and cover image
|
// without text and cover image
|
||||||
|
val entity = database.findBookById(book.id)
|
||||||
database.updateBooks(
|
database.updateBooks(
|
||||||
books.map {
|
listOf(
|
||||||
val book = database.findBookById(it.id)
|
|
||||||
bookMapper.toBookEntity(
|
bookMapper.toBookEntity(
|
||||||
it.copy(
|
book.copy(
|
||||||
textPath = book.textPath,
|
textPath = entity.textPath,
|
||||||
coverImage = if (book.image != null) Uri.parse(book.image) else null
|
coverImage = if (entity.image != null) Uri.parse(entity.image) else null
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateBookWithText(book: Book, text: List<String>): 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
|
// without cover image
|
||||||
val filesDir = application.filesDir
|
val filesDir = application.filesDir
|
||||||
val booksDir = File(filesDir, "books")
|
val booksDir = File(filesDir, "books")
|
||||||
|
|
||||||
if (!booksDir.exists()) {
|
if (!booksDir.exists()) {
|
||||||
|
Log.i(UPDATE_BOOK, "Created books folder.")
|
||||||
booksDir.mkdirs()
|
booksDir.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
val textUri: String
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,33 +279,35 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
FileOutputStream(textPath).use { stream ->
|
FileOutputStream(textPath).use { stream ->
|
||||||
text.forEach { line ->
|
bookWithText.text.forEach { line ->
|
||||||
stream.write(line.toByteArray())
|
stream.write(line.toByteArray())
|
||||||
stream.write(System.lineSeparator().toByteArray())
|
stream.write(System.lineSeparator().toByteArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(UPDATE_BOOK, "Could not update text.")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (book.textPath.isNotBlank()) {
|
if (bookWithText.book.textPath.isNotBlank()) {
|
||||||
try {
|
try {
|
||||||
val fileToDelete = File(
|
val fileToDelete = File(
|
||||||
book.textPath
|
bookWithText.book.textPath
|
||||||
)
|
)
|
||||||
|
|
||||||
if (fileToDelete.exists()) {
|
if (fileToDelete.exists()) {
|
||||||
fileToDelete.delete()
|
fileToDelete.delete()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(UPDATE_BOOK, "Failed to delete old text.")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val updatedBook = bookMapper.toBookEntity(
|
val updatedBook = bookMapper.toBookEntity(
|
||||||
book.copy(
|
bookWithText.book.copy(
|
||||||
textPath = "$booksDir/$textUri",
|
textPath = "$booksDir/$textUri",
|
||||||
coverImage = if (bookEntity.image != null) Uri.parse(bookEntity.image) else null
|
coverImage = if (bookEntity.image != null) Uri.parse(bookEntity.image) else null
|
||||||
)
|
)
|
||||||
|
|
@ -276,13 +316,19 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
database.updateBooks(
|
database.updateBooks(
|
||||||
listOf(updatedBook)
|
listOf(updatedBook)
|
||||||
)
|
)
|
||||||
|
Log.i(UPDATE_BOOK, "Successfully updated book.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update cover image of the book. Deletes old cover and replaces with new.
|
||||||
|
*/
|
||||||
override suspend fun updateCoverImageOfBook(
|
override suspend fun updateCoverImageOfBook(
|
||||||
bookWithOldCover: Book,
|
bookWithOldCover: Book,
|
||||||
newCoverImage: CoverImage?
|
newCoverImage: CoverImage?
|
||||||
) {
|
) {
|
||||||
|
Log.i(UPDATE_BOOK, "Updating cover image: ${bookWithOldCover.title}.")
|
||||||
|
|
||||||
// without text
|
// without text
|
||||||
val book = database.findBookById(bookWithOldCover.id)
|
val book = database.findBookById(bookWithOldCover.id)
|
||||||
var uri: String? = null
|
var uri: String? = null
|
||||||
|
|
@ -291,6 +337,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
val coversDir = File(filesDir, "covers")
|
val coversDir = File(filesDir, "covers")
|
||||||
|
|
||||||
if (!coversDir.exists()) {
|
if (!coversDir.exists()) {
|
||||||
|
Log.i(UPDATE_BOOK, "Created covers folder.")
|
||||||
coversDir.mkdirs()
|
coversDir.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,6 +360,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(UPDATE_BOOK, "Could not save new cover.")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -328,6 +376,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
fileToDelete.delete()
|
fileToDelete.delete()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(UPDATE_BOOK, "Could not delete old cover.")
|
||||||
e.printStackTrace()
|
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<Book>) {
|
override suspend fun deleteBooks(books: List<Book>) {
|
||||||
|
Log.i(DELETE_BOOKS, "Deleting books.")
|
||||||
|
|
||||||
val filesDir = application.filesDir
|
val filesDir = application.filesDir
|
||||||
val coversDir = File(filesDir, "covers")
|
val coversDir = File(filesDir, "covers")
|
||||||
val booksDir = File(filesDir, "books")
|
val booksDir = File(filesDir, "books")
|
||||||
|
|
||||||
if (!coversDir.exists()) {
|
if (!coversDir.exists()) {
|
||||||
|
Log.i(DELETE_BOOKS, "Created covers folder.")
|
||||||
coversDir.mkdirs()
|
coversDir.mkdirs()
|
||||||
}
|
}
|
||||||
if (!booksDir.exists()) {
|
if (!booksDir.exists()) {
|
||||||
|
Log.i(DELETE_BOOKS, "Created books folder.")
|
||||||
booksDir.mkdirs()
|
booksDir.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,6 +436,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
fileToDelete.delete()
|
fileToDelete.delete()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(DELETE_BOOKS, "Could not delete cover image.")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -390,6 +449,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
fileToDelete.delete()
|
fileToDelete.delete()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(DELETE_BOOKS, "Could not delete text.")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -397,15 +457,21 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
book
|
book
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Log.i(DELETE_BOOKS, "Successfully deleted books.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether can reset cover image (restore default).
|
||||||
|
*/
|
||||||
override suspend fun canResetCover(bookId: Int): Boolean {
|
override suspend fun canResetCover(bookId: Int): Boolean {
|
||||||
val book = database.findBookById(bookId)
|
val book = database.findBookById(bookId)
|
||||||
|
|
||||||
val defaultCoverUncompressed = fileParser.parse(File(book.filePath))?.second
|
val defaultCoverUncompressed = fileParser.parse(File(book.filePath))?.coverImage
|
||||||
?: return false
|
?: return false
|
||||||
|
|
||||||
if (book.image == null) {
|
if (book.image == null) {
|
||||||
|
Log.i(CAN_RESET_COVER, "Can reset cover image. (current is null)")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -424,6 +490,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
Uri.parse(book.image)
|
Uri.parse(book.image)
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.i(CAN_RESET_COVER, "Can reset cover image. (could not get current)")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -431,30 +498,45 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
return !defaultCover.sameAs(currentCover)
|
return !defaultCover.sameAs(currentCover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset cover image to default.
|
||||||
|
* If there is no default cover, returns false.
|
||||||
|
*/
|
||||||
override suspend fun resetCoverImage(bookId: Int): Boolean {
|
override suspend fun resetCoverImage(bookId: Int): Boolean {
|
||||||
if (!canResetCover(bookId)) {
|
if (!canResetCover(bookId)) {
|
||||||
|
Log.w(RESET_COVER, "Cannot reset cover image.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val book = database.findBookById(bookId)
|
val book = database.findBookById(bookId)
|
||||||
val defaultCover = fileParser.parse(File(book.filePath))?.second
|
val defaultCover = fileParser.parse(File(book.filePath))?.coverImage
|
||||||
?: return false
|
?: return false
|
||||||
updateCoverImageOfBook(bookMapper.toBook(book), defaultCover)
|
updateCoverImageOfBook(bookMapper.toBook(book), defaultCover)
|
||||||
|
|
||||||
|
Log.i(RESET_COVER, "Successfully reset cover image.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts DataStore constant to [DataStore].
|
||||||
|
*/
|
||||||
override suspend fun <T> putDataToDataStore(key: Preferences.Key<T>, value: T) {
|
override suspend fun <T> putDataToDataStore(key: Preferences.Key<T>, value: T) {
|
||||||
dataStore.putData(key, value)
|
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<MainState>()
|
val result = CompletableDeferred<MainState>()
|
||||||
|
|
||||||
scope.launch {
|
withContext(Dispatchers.Default) {
|
||||||
val keys = dataStore.getAllData()
|
val keys = dataStore.getAllData()
|
||||||
val data = mutableMapOf<String, Any>()
|
val data = mutableMapOf<String, Any>()
|
||||||
|
|
||||||
|
Log.i(GET_ALL_SETTINGS, "Got ${keys?.size} settings keys.")
|
||||||
|
|
||||||
val jobs = keys?.map { key ->
|
val jobs = keys?.map { key ->
|
||||||
async {
|
async {
|
||||||
val nullableData = dataStore.getNullableData(key)
|
val nullableData = dataStore.getNullableData(key)
|
||||||
|
|
@ -474,7 +556,13 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
return result.await()
|
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<SelectableFile> {
|
override suspend fun getFilesFromDevice(query: String): List<SelectableFile> {
|
||||||
|
Log.i(GET_FILES_FROM_DEVICE, "Getting files from device by query: \"$query\".")
|
||||||
|
|
||||||
val existingBooks = database
|
val existingBooks = database
|
||||||
.searchBooks("")
|
.searchBooks("")
|
||||||
.map { bookMapper.toBook(it) }
|
.map { bookMapper.toBook(it) }
|
||||||
|
|
@ -564,9 +652,11 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
(Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED &&
|
(Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED &&
|
||||||
Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED_READ_ONLY)
|
Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED_READ_ONLY)
|
||||||
) {
|
) {
|
||||||
|
Log.e(GET_FILES_FROM_DEVICE, "Could not correctly get root directory.")
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i(GET_FILES_FROM_DEVICE, "Successfully got all matching files.")
|
||||||
return rootDirectory.getAllFiles()
|
return rootDirectory.getAllFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -576,74 +666,106 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
override suspend fun getBookFromFile(file: File): NullableBook {
|
override suspend fun getBookFromFile(file: File): NullableBook {
|
||||||
val parsedBook = fileParser.parse(file)
|
val parsedBook = fileParser.parse(file)
|
||||||
if (parsedBook == null) {
|
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(
|
return NullableBook.Null(
|
||||||
file.name,
|
file.name,
|
||||||
UIText.StringResource(R.string.error_wrong_file_format)
|
UIText.StringResource(R.string.error_something_went_wrong)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parsedText = textParser.parse(file)
|
val parsedText = textParser.parse(file)
|
||||||
if (parsedText is Resource.Error) {
|
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(
|
return NullableBook.Null(
|
||||||
file.name,
|
file.name,
|
||||||
parsedText.message
|
parsedText.message
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i(GET_BOOK_FROM_FILE, "Successfully got book from file.")
|
||||||
return NullableBook.NotNull(
|
return NullableBook.NotNull(
|
||||||
book = parsedBook.first.copy(
|
bookWithTextAndCover = BookWithTextAndCover(
|
||||||
chapters = parsedText.data!!.map { it.chapter }.run {
|
book = parsedBook.book.copy(
|
||||||
if (this.size == 1) return@run emptyList()
|
chapters = parsedText.data!!.map { it.chapter }.run {
|
||||||
this
|
if (this.size == 1) return@run emptyList()
|
||||||
}
|
this
|
||||||
),
|
}
|
||||||
coverImage = parsedBook.second,
|
),
|
||||||
text = parsedText.data.map {
|
coverImage = parsedBook.coverImage,
|
||||||
it.text
|
text = parsedText.data.map {
|
||||||
}.flatten()
|
it.text
|
||||||
|
}.flatten()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun insertHistory(history: List<History>) {
|
/**
|
||||||
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<Resource<List<History>>> {
|
/**
|
||||||
return flow {
|
* Get all history from database.
|
||||||
val history = database.getHistory()
|
*/
|
||||||
|
override suspend fun getHistory(): List<History> {
|
||||||
emit(
|
return database.getHistory().map {
|
||||||
Resource.Success(
|
historyMapper.toHistory(
|
||||||
data = history.map {
|
it
|
||||||
historyMapper.toHistory(
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get latest history of the matching [bookId].
|
||||||
|
*/
|
||||||
override suspend fun getLatestBookHistory(bookId: Int): History? {
|
override suspend fun getLatestBookHistory(bookId: Int): History? {
|
||||||
val history = database.getLatestHistoryForBook(bookId)
|
val history = database.getLatestHistoryForBook(bookId)
|
||||||
return history?.let { historyMapper.toHistory(it) }
|
return history?.let { historyMapper.toHistory(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete whole history.
|
||||||
|
*/
|
||||||
override suspend fun deleteWholeHistory() {
|
override suspend fun deleteWholeHistory() {
|
||||||
database.deleteWholeHistory()
|
database.deleteWholeHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all history of the matching [bookId].
|
||||||
|
*/
|
||||||
override suspend fun deleteBookHistory(bookId: Int) {
|
override suspend fun deleteBookHistory(bookId: Int) {
|
||||||
database.deleteBookHistory(bookId)
|
database.deleteBookHistory(bookId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteHistory(history: List<History>) {
|
/**
|
||||||
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? {
|
override suspend fun checkForUpdates(postNotification: Boolean): LatestReleaseInfo? {
|
||||||
|
Log.i(CHECK_FOR_UPDATES, "Checking for updates. Post notification: $postNotification")
|
||||||
|
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val result = githubAPI.getLatestRelease()
|
val result = githubAPI.getLatestRelease()
|
||||||
|
|
@ -652,6 +774,7 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
val currentVersion = application.getString(R.string.app_version)
|
val currentVersion = application.getString(R.string.app_version)
|
||||||
|
|
||||||
if (version != currentVersion && postNotification) {
|
if (version != currentVersion && postNotification) {
|
||||||
|
Log.i(CHECK_FOR_UPDATES, "Posting notification.")
|
||||||
updatesNotificationService.postNotification(
|
updatesNotificationService.postNotification(
|
||||||
result
|
result
|
||||||
)
|
)
|
||||||
|
|
@ -659,12 +782,16 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
|
|
||||||
result
|
result
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(CHECK_FOR_UPDATES, "Could not get latest release information.")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update color preset.
|
||||||
|
*/
|
||||||
override suspend fun updateColorPreset(colorPreset: ColorPreset) {
|
override suspend fun updateColorPreset(colorPreset: ColorPreset) {
|
||||||
database.updateColorPreset(
|
database.updateColorPreset(
|
||||||
colorPresetMapper.toColorPresetEntity(
|
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) {
|
override suspend fun selectColorPreset(colorPreset: ColorPreset) {
|
||||||
database.getColorPresets().map {
|
database.getColorPresets().map {
|
||||||
it.copy(
|
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<ColorPreset> {
|
override suspend fun getColorPresets(): List<ColorPreset> {
|
||||||
return database.getColorPresets()
|
return database.getColorPresets()
|
||||||
.sortedBy { it.order }
|
.sortedBy { it.order }
|
||||||
.map { colorPresetMapper.toColorPreset(it) }
|
.map { colorPresetMapper.toColorPreset(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder color presets.
|
||||||
|
* Changes the order of the color presets.
|
||||||
|
*/
|
||||||
override suspend fun reorderColorPresets(orderedColorPresets: List<ColorPreset>) {
|
override suspend fun reorderColorPresets(orderedColorPresets: List<ColorPreset>) {
|
||||||
database.deleteColorPresets()
|
database.deleteColorPresets()
|
||||||
|
|
||||||
|
|
@ -701,6 +839,9 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete color preset.
|
||||||
|
*/
|
||||||
override suspend fun deleteColorPreset(colorPreset: ColorPreset) {
|
override suspend fun deleteColorPreset(colorPreset: ColorPreset) {
|
||||||
database.deleteColorPreset(
|
database.deleteColorPreset(
|
||||||
colorPresetMapper.toColorPresetEntity(
|
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) {
|
override suspend fun updateFavoriteDirectory(path: String) {
|
||||||
if (database.favoriteDirectoryExits(path)) {
|
if (database.favoriteDirectoryExits(path)) {
|
||||||
database.deleteFavoriteDirectory(
|
database.deleteFavoriteDirectory(
|
||||||
|
|
@ -722,18 +868,3 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package ua.acclorite.book_story.domain.model
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import ua.acclorite.book_story.domain.util.CoverImage
|
||||||
import ua.acclorite.book_story.domain.util.UIText
|
import ua.acclorite.book_story.domain.util.UIText
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -24,3 +25,46 @@ data class Book(
|
||||||
val lastOpened: Long?,
|
val lastOpened: Long?,
|
||||||
val category: Category,
|
val category: Category,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class BookWithText(
|
||||||
|
val book: Book,
|
||||||
|
val text: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class BookWithCover(
|
||||||
|
val book: Book,
|
||||||
|
val coverImage: CoverImage?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class BookWithTextAndCover(
|
||||||
|
val book: Book,
|
||||||
|
val coverImage: CoverImage?,
|
||||||
|
val text: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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<String> = emptyList(),
|
|
||||||
val fileName: String?,
|
|
||||||
val message: UIText?
|
|
||||||
) {
|
|
||||||
class NotNull(
|
|
||||||
book: Book,
|
|
||||||
coverImage: CoverImage?,
|
|
||||||
text: List<String>
|
|
||||||
) : 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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
package ua.acclorite.book_story.domain.repository
|
package ua.acclorite.book_story.domain.repository
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
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.data.remote.dto.LatestReleaseInfo
|
||||||
import ua.acclorite.book_story.domain.model.Book
|
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.ColorPreset
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
import ua.acclorite.book_story.domain.model.SelectableFile
|
import ua.acclorite.book_story.domain.model.SelectableFile
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
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 ua.acclorite.book_story.presentation.data.MainState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
interface BookRepository {
|
interface BookRepository {
|
||||||
|
|
||||||
|
/* ------ Books ------------------------------ */
|
||||||
suspend fun getBooks(
|
suspend fun getBooks(
|
||||||
query: String
|
query: String
|
||||||
): List<Book>
|
): List<Book>
|
||||||
|
|
@ -29,18 +29,15 @@ interface BookRepository {
|
||||||
): List<String>
|
): List<String>
|
||||||
|
|
||||||
suspend fun insertBook(
|
suspend fun insertBook(
|
||||||
book: Book,
|
bookWithTextAndCover: BookWithTextAndCover
|
||||||
coverImage: CoverImage?,
|
|
||||||
text: List<String>
|
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
suspend fun updateBooks(
|
suspend fun updateBook(
|
||||||
books: List<Book>
|
book: Book
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun updateBookWithText(
|
suspend fun updateBookWithText(
|
||||||
book: Book,
|
bookWithText: BookWithText
|
||||||
text: List<String>
|
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
suspend fun updateCoverImageOfBook(
|
suspend fun updateCoverImageOfBook(
|
||||||
|
|
@ -56,49 +53,85 @@ interface BookRepository {
|
||||||
bookId: Int
|
bookId: Int
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
suspend fun resetCoverImage(bookId: Int): Boolean
|
suspend fun resetCoverImage(
|
||||||
|
bookId: Int
|
||||||
|
): Boolean
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
|
||||||
|
/* ------ DataStore -------------------------- */
|
||||||
suspend fun <T> putDataToDataStore(
|
suspend fun <T> putDataToDataStore(
|
||||||
key: Preferences.Key<T>,
|
key: Preferences.Key<T>,
|
||||||
value: T
|
value: T
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun getAllSettings(): MainState
|
||||||
suspend fun getAllSettings(scope: CoroutineScope): MainState
|
/* - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
suspend fun getFilesFromDevice(query: String = ""): List<SelectableFile>
|
|
||||||
|
|
||||||
suspend fun getBookFromFile(file: File): NullableBook
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun insertHistory(history: List<History>)
|
/* ------ File System ------------------------ */
|
||||||
|
suspend fun getFilesFromDevice(
|
||||||
|
query: String = ""
|
||||||
|
): List<SelectableFile>
|
||||||
|
|
||||||
suspend fun getHistory(): Flow<Resource<List<History>>>
|
suspend fun getBookFromFile(
|
||||||
|
file: File
|
||||||
|
): NullableBook
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
suspend fun getLatestBookHistory(bookId: Int): History?
|
|
||||||
|
/* ------ History ---------------------------- */
|
||||||
|
suspend fun insertHistory(
|
||||||
|
history: History
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun getHistory(): List<History>
|
||||||
|
|
||||||
|
suspend fun getLatestBookHistory(
|
||||||
|
bookId: Int
|
||||||
|
): History?
|
||||||
|
|
||||||
suspend fun deleteWholeHistory()
|
suspend fun deleteWholeHistory()
|
||||||
|
|
||||||
suspend fun deleteBookHistory(bookId: Int)
|
suspend fun deleteBookHistory(
|
||||||
|
bookId: Int
|
||||||
suspend fun deleteHistory(
|
|
||||||
history: List<History>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun deleteHistory(
|
||||||
suspend fun checkForUpdates(postNotification: Boolean): LatestReleaseInfo?
|
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<ColorPreset>
|
suspend fun getColorPresets(): List<ColorPreset>
|
||||||
|
|
||||||
suspend fun reorderColorPresets(orderedColorPresets: List<ColorPreset>)
|
suspend fun reorderColorPresets(
|
||||||
|
orderedColorPresets: List<ColorPreset>
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun deleteColorPreset(colorPreset: ColorPreset)
|
suspend fun deleteColorPreset(
|
||||||
|
colorPreset: ColorPreset
|
||||||
|
)
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
|
||||||
|
/* ------ Favorite Directories --------------- */
|
||||||
suspend fun updateFavoriteDirectory(path: String)
|
suspend fun updateFavoriteDirectory(path: String)
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class DeleteHistory @Inject constructor(private val repository: BookRepository) {
|
class DeleteHistory @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
suspend fun execute(history: List<History>) {
|
suspend fun execute(history: History) {
|
||||||
repository.deleteHistory(history)
|
repository.deleteHistory(history)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package ua.acclorite.book_story.domain.use_case
|
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.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.presentation.data.MainState
|
import ua.acclorite.book_story.presentation.data.MainState
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -9,7 +8,7 @@ class GetAllSettings @Inject constructor(
|
||||||
private val repository: BookRepository
|
private val repository: BookRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(scope: CoroutineScope): MainState {
|
suspend fun execute(): MainState {
|
||||||
return repository.getAllSettings(scope)
|
return repository.getAllSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
package ua.acclorite.book_story.domain.use_case
|
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.model.History
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import ua.acclorite.book_story.domain.util.Resource
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetHistory @Inject constructor(private val repository: BookRepository) {
|
class GetHistory @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
suspend fun execute(): Flow<Resource<List<History>>> {
|
suspend fun execute(): List<History> {
|
||||||
return repository.getHistory()
|
return repository.getHistory()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
package ua.acclorite.book_story.domain.use_case
|
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.repository.BookRepository
|
||||||
import ua.acclorite.book_story.domain.util.CoverImage
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class InsertBook @Inject constructor(private val repository: BookRepository) {
|
class InsertBook @Inject constructor(private val repository: BookRepository) {
|
||||||
suspend fun execute(
|
suspend fun execute(
|
||||||
book: Book,
|
bookWithTextAndCover: BookWithTextAndCover
|
||||||
coverImage: CoverImage?,
|
|
||||||
text: List<String>
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return repository.insertBook(book, coverImage, text)
|
return repository.insertBook(bookWithTextAndCover)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,7 @@ import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class InsertHistory @Inject constructor(private val repository: BookRepository) {
|
class InsertHistory @Inject constructor(private val repository: BookRepository) {
|
||||||
suspend fun execute(history: List<History>) {
|
suspend fun execute(history: History) {
|
||||||
repository.insertHistory(history)
|
repository.insertHistory(history)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,6 @@ import javax.inject.Inject
|
||||||
class UpdateBook @Inject constructor(private val repository: BookRepository) {
|
class UpdateBook @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
suspend fun execute(book: Book) {
|
suspend fun execute(book: Book) {
|
||||||
repository.updateBooks(listOf(book))
|
repository.updateBook(book)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package ua.acclorite.book_story.domain.use_case
|
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 ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class UpdateBookWithText @Inject constructor(private val repository: BookRepository) {
|
class UpdateBookWithText @Inject constructor(private val repository: BookRepository) {
|
||||||
|
|
||||||
suspend fun execute(book: Book, text: List<String>): Boolean {
|
suspend fun execute(bookWithText: BookWithText): Boolean {
|
||||||
return repository.updateBookWithText(book, text)
|
return repository.updateBookWithText(bookWithText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -468,7 +468,7 @@ class MainViewModel @Inject constructor(
|
||||||
settingsViewModel: SettingsViewModel,
|
settingsViewModel: SettingsViewModel,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
val settings = getAllSettings.execute(viewModelScope)
|
val settings = getAllSettings.execute()
|
||||||
|
|
||||||
// All additional execution
|
// All additional execution
|
||||||
changeLanguage.execute(settings.language)
|
changeLanguage.execute(settings.language)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import ua.acclorite.book_story.R
|
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.Category
|
||||||
import ua.acclorite.book_story.domain.model.History
|
import ua.acclorite.book_story.domain.model.History
|
||||||
import ua.acclorite.book_story.domain.model.NullableBook
|
import ua.acclorite.book_story.domain.model.NullableBook
|
||||||
|
|
@ -531,7 +532,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
|
|
||||||
var textUpdated = false
|
var textUpdated = false
|
||||||
|
|
||||||
val updatedText = updatedBook.text
|
val updatedText = updatedBook.bookWithTextAndCover!!.text
|
||||||
val text = getText.execute(book.textPath)
|
val text = getText.execute(book.textPath)
|
||||||
|
|
||||||
if (updatedText != text) {
|
if (updatedText != text) {
|
||||||
|
|
@ -560,7 +561,7 @@ class BookInfoViewModel @Inject constructor(
|
||||||
yield()
|
yield()
|
||||||
onEvent(
|
onEvent(
|
||||||
BookInfoEvent.OnShowConfirmUpdateDialog(
|
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 updatedText = _state.value.updatedText ?: return@launch
|
||||||
val isSuccess = updateBookWithText.execute(
|
val isSuccess = updateBookWithText.execute(
|
||||||
book = _state.value.book,
|
BookWithText(
|
||||||
text = updatedText
|
book = _state.value.book,
|
||||||
|
text = updatedText
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isSuccess) {
|
if (!isSuccess) {
|
||||||
|
|
@ -732,12 +735,10 @@ class BookInfoViewModel @Inject constructor(
|
||||||
onEvent(BookInfoEvent.OnCancelUpdate)
|
onEvent(BookInfoEvent.OnCancelUpdate)
|
||||||
_state.value.book.id.let {
|
_state.value.book.id.let {
|
||||||
insertHistory.execute(
|
insertHistory.execute(
|
||||||
listOf(
|
History(
|
||||||
History(
|
bookId = it,
|
||||||
bookId = it,
|
book = null,
|
||||||
book = null,
|
time = Date().time
|
||||||
time = Date().time
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,14 +46,14 @@ fun BrowseAddingDialogItem(result: Pair<NullableBook, Selected>, onClick: (Boole
|
||||||
Modifier.weight(1f)
|
Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = result.first.book!!.title,
|
text = result.first.bookWithTextAndCover!!.book.title,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = result.first.book!!.author.asString(),
|
text = result.first.bookWithTextAndCover!!.book.author.asString(),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
|
|
||||||
|
|
@ -467,12 +467,11 @@ class BrowseViewModel @Inject constructor(
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val failed = booksToInsert.any {
|
var failed = false
|
||||||
!insertBook.execute(
|
booksToInsert.forEach {
|
||||||
it.book!!,
|
if (!insertBook.execute(it.bookWithTextAndCover!!)) {
|
||||||
it.coverImage,
|
failed = true
|
||||||
it.text
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failed) {
|
if (failed) {
|
||||||
|
|
|
||||||
|
|
@ -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.GetBooksById
|
||||||
import ua.acclorite.book_story.domain.use_case.GetHistory
|
import ua.acclorite.book_story.domain.use_case.GetHistory
|
||||||
import ua.acclorite.book_story.domain.use_case.InsertHistory
|
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.navigation.Screen
|
||||||
import ua.acclorite.book_story.presentation.core.util.BaseViewModel
|
import ua.acclorite.book_story.presentation.core.util.BaseViewModel
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
@ -119,9 +118,7 @@ class HistoryViewModel @Inject constructor(
|
||||||
|
|
||||||
is HistoryEvent.OnDeleteHistoryElement -> {
|
is HistoryEvent.OnDeleteHistoryElement -> {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
deleteHistory.execute(
|
deleteHistory.execute(event.historyToDelete)
|
||||||
listOf(event.historyToDelete)
|
|
||||||
)
|
|
||||||
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
@ -154,9 +151,7 @@ class HistoryViewModel @Inject constructor(
|
||||||
when (snackbar) {
|
when (snackbar) {
|
||||||
SnackbarResult.Dismissed -> Unit
|
SnackbarResult.Dismissed -> Unit
|
||||||
SnackbarResult.ActionPerformed -> {
|
SnackbarResult.ActionPerformed -> {
|
||||||
insertHistory.execute(
|
insertHistory.execute(event.historyToDelete)
|
||||||
listOf(event.historyToDelete)
|
|
||||||
)
|
|
||||||
event.refreshList()
|
event.refreshList()
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
@ -256,12 +251,10 @@ class HistoryViewModel @Inject constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
event.book.id.let {
|
event.book.id.let {
|
||||||
insertHistory.execute(
|
insertHistory.execute(
|
||||||
listOf(
|
History(
|
||||||
History(
|
bookId = it,
|
||||||
bookId = it,
|
book = null,
|
||||||
book = null,
|
time = Date().time
|
||||||
time = Date().time
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -300,91 +293,82 @@ class HistoryViewModel @Inject constructor(
|
||||||
return maxElementsById.filterNotNull()
|
return maxElementsById.filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistory.execute().collect { result ->
|
val historyWithoutBooks = getHistory.execute()
|
||||||
when (result) {
|
.sortedByDescending { it.time }
|
||||||
is Resource.Success -> {
|
|
||||||
val historyWithoutBook =
|
|
||||||
result.data?.sortedByDescending { it.time } ?: emptyList()
|
|
||||||
|
|
||||||
if (historyWithoutBook.isEmpty()) {
|
if (historyWithoutBooks.isEmpty()) {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
history = emptyList(),
|
history = emptyList(),
|
||||||
isLoading = false
|
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<GroupedHistory>()
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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<GroupedHistory>()
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -328,12 +328,10 @@ class LibraryViewModel @Inject constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
event.book.id.let {
|
event.book.id.let {
|
||||||
insertHistory.execute(
|
insertHistory.execute(
|
||||||
listOf(
|
History(
|
||||||
History(
|
bookId = it,
|
||||||
bookId = it,
|
book = null,
|
||||||
book = null,
|
time = Date().time
|
||||||
time = Date().time
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue