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