From fb39d4fe15615236da04b45a4ddc2dae8dd1ce2f Mon Sep 17 00:00:00 2001 From: Acclorite <140836141+Acclorite@users.noreply.github.com> Date: Wed, 1 Jan 2025 12:49:58 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Reformat=20parsers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reformatted parsers * Direct parsing * Scalable * Slightly faster parsing (no optimizations yet) Part-of: #134 --- .../book_story/data/parser/TextParser.kt | 5 +- .../book_story/data/parser/TextParserImpl.kt | 15 +- .../data/parser/epub/EpubTextParser.kt | 154 +++++++++--------- .../data/parser/fb2/Fb2TextParser.kt | 79 ++++----- .../data/parser/html/HtmlTextParser.kt | 77 +++++---- .../data/parser/pdf/PdfTextParser.kt | 72 ++++---- .../data/parser/txt/TxtTextParser.kt | 74 ++++----- .../data/repository/BookRepositoryImpl.kt | 44 ++--- .../book_story/domain/reader/Chapter.kt | 14 -- .../domain/reader/ChapterWithText.kt | 9 - .../domain/reader/ChaptersAndText.kt | 10 -- .../book_story/domain/reader/ReaderText.kt | 16 ++ .../domain/repository/BookRepository.kt | 4 +- .../domain/use_case/book/GetText.kt | 4 +- .../book_story/domain/util/Resource.kt | 8 - .../core/util/PrimitiveExtensions.kt | 3 +- .../presentation/reader/ReaderBottomBar.kt | 6 +- .../reader/ReaderChaptersDrawer.kt | 22 ++- .../presentation/reader/ReaderContent.kt | 11 +- .../presentation/reader/ReaderDrawer.kt | 2 +- .../presentation/reader/ReaderLayout.kt | 18 +- .../reader/ReaderLayoutParagraph.kt | 120 -------------- .../presentation/reader/ReaderLayoutText.kt | 73 +++++++++ ...tChapter.kt => ReaderLayoutTextChapter.kt} | 28 ++-- .../reader/ReaderLayoutTextParagraph.kt | 104 ++++++++++++ .../reader/ReaderLayoutTextSeparator.kt | 25 +++ .../presentation/reader/ReaderScaffold.kt | 8 +- .../presentation/reader/ReaderTopBar.kt | 2 +- .../book_story/ui/reader/ReaderEvent.kt | 3 +- .../book_story/ui/reader/ReaderModel.kt | 95 +++++++---- .../book_story/ui/reader/ReaderScreen.kt | 10 -- .../book_story/ui/reader/ReaderState.kt | 7 +- 32 files changed, 594 insertions(+), 528 deletions(-) delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/reader/Chapter.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/reader/ChapterWithText.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/reader/ChaptersAndText.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/reader/ReaderText.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/util/Resource.kt delete mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutParagraph.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutText.kt rename app/src/main/java/ua/acclorite/book_story/presentation/reader/{ReaderLayoutChapter.kt => ReaderLayoutTextChapter.kt} (51%) create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextParagraph.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextSeparator.kt diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt index 0a466927..76cfe9c9 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParser.kt @@ -1,9 +1,8 @@ package ua.acclorite.book_story.data.parser -import ua.acclorite.book_story.domain.reader.ChapterWithText -import ua.acclorite.book_story.domain.util.Resource +import ua.acclorite.book_story.domain.reader.ReaderText import java.io.File interface TextParser { - suspend fun parse(file: File): Resource> + suspend fun parse(file: File): List } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParserImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParserImpl.kt index df5536a4..1b97e632 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/TextParserImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/TextParserImpl.kt @@ -1,15 +1,12 @@ package ua.acclorite.book_story.data.parser import android.util.Log -import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.epub.EpubTextParser import ua.acclorite.book_story.data.parser.fb2.Fb2TextParser import ua.acclorite.book_story.data.parser.html.HtmlTextParser import ua.acclorite.book_story.data.parser.pdf.PdfTextParser import ua.acclorite.book_story.data.parser.txt.TxtTextParser -import ua.acclorite.book_story.domain.reader.ChapterWithText -import ua.acclorite.book_story.domain.ui.UIText -import ua.acclorite.book_story.domain.util.Resource +import ua.acclorite.book_story.domain.reader.ReaderText import java.io.File import javax.inject.Inject @@ -20,14 +17,12 @@ class TextParserImpl @Inject constructor( private val pdfTextParser: PdfTextParser, private val epubTextParser: EpubTextParser, private val fb2TextParser: Fb2TextParser, - private val htmlTextParser: HtmlTextParser, + private val htmlTextParser: HtmlTextParser ) : TextParser { - override suspend fun parse(file: File): Resource> { + override suspend fun parse(file: File): List { if (!file.exists()) { Log.e(TEXT_PARSER, "File does not exist.") - return Resource.Error( - UIText.StringResource(R.string.error_something_went_wrong_with_file) - ) + return emptyList() } val fileFormat = ".${file.extension}".lowercase().trim() @@ -66,7 +61,7 @@ class TextParserImpl @Inject constructor( else -> { Log.e(TEXT_PARSER, "Wrong file format, could not find supported extension.") - Resource.Error(UIText.StringResource(R.string.error_wrong_file_format)) + emptyList() } } } diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt index 62e0180f..57b93c7e 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt @@ -12,14 +12,12 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import org.jsoup.Jsoup -import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.DocumentParser +import ua.acclorite.book_story.data.parser.MarkdownParser import ua.acclorite.book_story.data.parser.TextParser -import ua.acclorite.book_story.domain.reader.Chapter -import ua.acclorite.book_story.domain.reader.ChapterWithText -import ua.acclorite.book_story.domain.ui.UIText -import ua.acclorite.book_story.domain.util.Resource +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.presentation.core.util.addAll +import ua.acclorite.book_story.presentation.core.util.clearAllMarkdown import ua.acclorite.book_story.presentation.core.util.clearMarkdown import java.io.File import java.util.concurrent.ConcurrentLinkedQueue @@ -34,16 +32,16 @@ private typealias Title = String private val dispatcher = Dispatchers.IO.limitedParallelism(2) class EpubTextParser @Inject constructor( + private val markdownParser: MarkdownParser, private val documentParser: DocumentParser ) : TextParser { - override suspend fun parse(file: File): Resource> { + override suspend fun parse(file: File): List { Log.i(EPUB_TAG, "Started EPUB parsing: ${file.name}.") return try { - val chapters = mutableListOf() - yield() + var readerText = listOf() withContext(Dispatchers.IO) { ZipFile(file).use { zip -> @@ -62,35 +60,28 @@ class EpubTextParser @Inject constructor( Log.i(EPUB_TAG, "Chapter entries, size: ${chapterEntries.size}") Log.i(EPUB_TAG, "Title entries, size: ${chapterTitleEntries?.size}") - zip.parseEpub( + readerText = zip.parseEpub( chapterEntries = chapterEntries, chapterTitleEntries = chapterTitleEntries - ).let { - if (it == null || it.isEmpty()) { - Log.e(EPUB_TAG, "Could not parse EPUB (null or empty).") - return@withContext - } - chapters.addAll(it) - } + ) } } yield() - if (chapters.isEmpty()) { - return Resource.Error(UIText.StringResource(R.string.error_file_empty)) + if ( + readerText.filterIsInstance().isEmpty() || + readerText.filterIsInstance().isEmpty() + ) { + Log.e(EPUB_TAG, "Could not extract text from EPUB.") + return emptyList() } Log.i(EPUB_TAG, "Successfully finished EPUB parsing.") - Resource.Success(chapters) + readerText } catch (e: Exception) { e.printStackTrace() - Resource.Error( - UIText.StringResource( - R.string.error_query, - e.message?.take(40)?.trim() ?: "" - ) - ) + emptyList() } } @@ -107,18 +98,18 @@ class EpubTextParser @Inject constructor( private suspend fun ZipFile.parseEpub( chapterEntries: List, chapterTitleEntries: Map>? - ): List? { + ): List { - val chapters = mutableListOf() + val readerText = mutableListOf() coroutineScope { - val unformattedChapters = ConcurrentLinkedQueue() + val unformattedText = ConcurrentLinkedQueue>>() // Asynchronously getting all chapters with text val jobs = chapterEntries.mapIndexed { index, entry -> async(dispatcher) { yield() - unformattedChapters.parseZipEntry( + unformattedText.parseZipEntry( zip = this@parseEpub, index = index, entry = entry, @@ -131,27 +122,15 @@ class EpubTextParser @Inject constructor( jobs.awaitAll() // Sorting chapters in correct order - chapters.addAll { - var textIndex = -1 - unformattedChapters.toList() - .sortedBy { it.chapter.index } - .mapIndexed { index, item -> - item.copy( - chapter = item.chapter.copy( - index = index, - startIndex = textIndex + 1, - endIndex = textIndex + item.text.size - ) - ).also { textIndex += item.text.size } - } + readerText.addAll { + unformattedText.toList() + .sortedBy { (index, _) -> index } + .map { it.second } + .flatten() } } - if (chapters.isEmpty()) { - return null - } - - return chapters + return readerText } /** @@ -163,7 +142,7 @@ class EpubTextParser @Inject constructor( * @param entry [ZipEntry]. * @param chapterTitleMap Titles from [getChapterTitleMapFromToc]. */ - private suspend fun ConcurrentLinkedQueue.parseZipEntry( + private suspend fun ConcurrentLinkedQueue>>.parseZipEntry( zip: ZipFile, index: Int, entry: ZipEntry, @@ -171,46 +150,67 @@ class EpubTextParser @Inject constructor( ) { // Getting all text val content = zip.getInputStream(entry).bufferedReader().use { it.readText() } - var chapter = documentParser.run { + var text = documentParser.run { Jsoup.parse(content).parseDocument() } + val readerText = mutableListOf() - if (chapter.isEmpty()) { - Log.w(EPUB_TAG, "Chapter ${entry.name} is empty.") - return - } - - // Getting title and removing first line (if matches title) - val chapterTitle = getChapterTitleFromToc( + // Adding chapter title from TOC if found + var chapterAdded = false + getChapterTitleFromToc( chapterSource = entry.name, chapterTitleMap = chapterTitleMap - ).run { - if (this != null) { - return@run this - } - chapter.first().clearMarkdown() - }.also { title -> - chapter = chapter.dropWhile { line -> - line.clearMarkdown().lowercase() == title.lowercase() + ).apply { + if (this == null) return@apply + readerText.add( + ReaderText.Chapter( + title = this + ) + ) + chapterAdded = true + + text = text.dropWhile { line -> + line.clearMarkdown().lowercase() == this.lowercase() } } - if (chapter.isEmpty()) { - Log.w(EPUB_TAG, "Chapter ${entry.name} is empty.") + // Format and add text + text.forEach { line -> + yield() + + if (line.isNotBlank()) { + when (line) { + "***", "---" -> readerText.add( + ReaderText.Separator + ) + + else -> { + if (!chapterAdded && line.clearAllMarkdown().isNotBlank()) { + readerText.add( + 0, ReaderText.Chapter( + title = line.clearAllMarkdown() + ) + ) + chapterAdded = true + } else readerText.add( + ReaderText.Text( + line = markdownParser.parse(line) + ) + ) + } + } + } + } + + if ( + readerText.filterIsInstance().isEmpty() || + readerText.filterIsInstance().isEmpty() + ) { + Log.w(EPUB_TAG, "Could not extract text from [${entry.name}].") return } - add( - ChapterWithText( - Chapter( - index = index, - title = chapterTitle, - startIndex = 0, - endIndex = 0 - ), - text = chapter - ) - ) + add(index to readerText) } /** diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2TextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2TextParser.kt index 3797373e..23663122 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2TextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2TextParser.kt @@ -6,12 +6,9 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import org.w3c.dom.Element import org.w3c.dom.NodeList -import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.parser.MarkdownParser import ua.acclorite.book_story.data.parser.TextParser -import ua.acclorite.book_story.domain.reader.Chapter -import ua.acclorite.book_story.domain.reader.ChapterWithText -import ua.acclorite.book_story.domain.ui.UIText -import ua.acclorite.book_story.domain.util.Resource +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.presentation.core.util.clearAllMarkdown import java.io.File import javax.inject.Inject @@ -19,9 +16,11 @@ import javax.xml.parsers.DocumentBuilderFactory private const val FB2_TAG = "FB2 Parser" -class Fb2TextParser @Inject constructor() : TextParser { +class Fb2TextParser @Inject constructor( + private val markdownParser: MarkdownParser +) : TextParser { - override suspend fun parse(file: File): Resource> { + override suspend fun parse(file: File): List { Log.i(FB2_TAG, "Started FB2 parsing: ${file.name}.") return try { @@ -31,13 +30,11 @@ class Fb2TextParser @Inject constructor() : TextParser { builder.parse(file) } - val formattedLines = mutableListOf() + val readerText = mutableListOf() val bodyNodes = document.getElementsByTagName("body") if (bodyNodes.length == 0) { - return Resource.Error( - UIText.StringResource(R.string.error_file_empty) - ) + return emptyList() } yield() @@ -108,45 +105,49 @@ class Fb2TextParser @Inject constructor() : TextParser { yield() + var chapterAdded = false lines.forEach { line -> yield() - formattedLines.add(line.trim()) + + if (line.isNotBlank()) { + when (line) { + "***", "---" -> readerText.add( + ReaderText.Separator + ) + + else -> { + if (!chapterAdded && line.clearAllMarkdown().isNotBlank()) { + readerText.add( + 0, ReaderText.Chapter( + title = line.clearAllMarkdown() + ) + ) + chapterAdded = true + } else readerText.add( + ReaderText.Text( + line = markdownParser.parse(line) + ) + ) + } + } + } } yield() - if (formattedLines.size < 2) { - return Resource.Error(UIText.StringResource(R.string.error_file_empty)) - } - - val title = formattedLines.first().clearAllMarkdown().let { title -> - formattedLines.removeAt(0) - if (title.isBlank()) return@let "Chapter 1" - return@let title + if ( + readerText.filterIsInstance().isEmpty() || + readerText.filterIsInstance().isEmpty() + ) { + Log.e(FB2_TAG, "Could not extract text from FB2.") + return emptyList() } Log.i(FB2_TAG, "Successfully finished FB2 parsing.") - Resource.Success( - listOf( - ChapterWithText( - chapter = Chapter( - index = 0, - title = title, - startIndex = 0, - endIndex = formattedLines.lastIndex - ), - text = formattedLines - ) - ) - ) + readerText } catch (e: Exception) { e.printStackTrace() - Resource.Error( - UIText.StringResource( - R.string.error_query, - e.message?.take(40)?.trim() ?: "" - ) - ) + emptyList() } } diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/html/HtmlTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/html/HtmlTextParser.kt index 5148b0b0..bc591c9c 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/html/HtmlTextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/html/HtmlTextParser.kt @@ -3,13 +3,10 @@ package ua.acclorite.book_story.data.parser.html import android.util.Log import kotlinx.coroutines.yield import org.jsoup.Jsoup -import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.DocumentParser +import ua.acclorite.book_story.data.parser.MarkdownParser import ua.acclorite.book_story.data.parser.TextParser -import ua.acclorite.book_story.domain.reader.Chapter -import ua.acclorite.book_story.domain.reader.ChapterWithText -import ua.acclorite.book_story.domain.ui.UIText -import ua.acclorite.book_story.domain.util.Resource +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.presentation.core.util.clearAllMarkdown import java.io.File import javax.inject.Inject @@ -17,52 +14,62 @@ import javax.inject.Inject private const val HTML_TAG = "HTML Parser" class HtmlTextParser @Inject constructor( + private val markdownParser: MarkdownParser, private val documentParser: DocumentParser ) : TextParser { - override suspend fun parse(file: File): Resource> { + override suspend fun parse(file: File): List { Log.i(HTML_TAG, "Started HTML parsing: ${file.name}.") return try { - val lines = documentParser.run { + var chapterAdded = false + val documentLines = documentParser.run { Jsoup.parse(file).parseDocument() - }.toMutableList() + } + val readerText = mutableListOf() + + for (line in documentLines) { + yield() + + if (line.isNotBlank()) { + when (line) { + "***", "---" -> readerText.add( + ReaderText.Separator + ) + + else -> { + if (!chapterAdded && line.clearAllMarkdown().isNotBlank()) { + readerText.add( + 0, ReaderText.Chapter( + title = line.clearAllMarkdown() + ) + ) + chapterAdded = true + } else readerText.add( + ReaderText.Text( + line = markdownParser.parse(line) + ) + ) + } + } + } + } yield() - if (lines.size < 2) { + if ( + readerText.filterIsInstance().isEmpty() || + readerText.filterIsInstance().isEmpty() + ) { Log.e(HTML_TAG, "Could not extract text from HTML.") - return Resource.Error(UIText.StringResource(R.string.error_file_empty)) - } - - val title = lines.first().clearAllMarkdown().let { title -> - lines.removeAt(0) - if (title.isBlank()) return@let "Chapter 1" - return@let title + return emptyList() } Log.i(HTML_TAG, "Successfully finished HTML parsing.") - Resource.Success( - listOf( - ChapterWithText( - chapter = Chapter( - index = 0, - title = title, - startIndex = 0, - endIndex = lines.lastIndex - ), - text = lines - ) - ) - ) + readerText } catch (e: Exception) { e.printStackTrace() - Resource.Error( - UIText.StringResource( - R.string.error_query, - e.message?.take(40)?.trim() ?: "" - ) - ) + emptyList() } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt index fe499ee3..f79200ca 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/pdf/PdfTextParser.kt @@ -6,12 +6,9 @@ import com.tom_roush.pdfbox.android.PDFBoxResourceLoader import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.text.PDFTextStripper import kotlinx.coroutines.yield -import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.parser.MarkdownParser import ua.acclorite.book_story.data.parser.TextParser -import ua.acclorite.book_story.domain.reader.Chapter -import ua.acclorite.book_story.domain.reader.ChapterWithText -import ua.acclorite.book_story.domain.ui.UIText -import ua.acclorite.book_story.domain.util.Resource +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.presentation.core.util.clearAllMarkdown import java.io.File import javax.inject.Inject @@ -19,10 +16,11 @@ import javax.inject.Inject private const val PDF_TAG = "PDF Parser" class PdfTextParser @Inject constructor( + private val markdownParser: MarkdownParser, private val application: Application ) : TextParser { - override suspend fun parse(file: File): Resource> { + override suspend fun parse(file: File): List { Log.i(PDF_TAG, "Started PDF parsing: ${file.name}.") return try { @@ -44,7 +42,7 @@ class PdfTextParser @Inject constructor( yield() - val strings = mutableListOf() + val readerText = mutableListOf() val text = oldText.filterIndexed { index, c -> yield() @@ -110,45 +108,49 @@ class PdfTextParser @Inject constructor( yield() + var chapterAdded = false lines.forEach { line -> yield() - strings.add(line.trim()) + + if (line.isNotBlank()) { + when (line) { + "***", "---" -> readerText.add( + ReaderText.Separator + ) + + else -> { + if (!chapterAdded && line.clearAllMarkdown().isNotBlank()) { + readerText.add( + 0, ReaderText.Chapter( + title = line.clearAllMarkdown() + ) + ) + chapterAdded = true + } else readerText.add( + ReaderText.Text( + line = markdownParser.parse(line) + ) + ) + } + } + } } yield() - if (strings.size < 2) { - return Resource.Error(UIText.StringResource(R.string.error_file_empty)) - } - - val title = strings.first().clearAllMarkdown().let { title -> - strings.removeAt(0) - if (title.isBlank()) return@let "Chapter 1" - return@let title + if ( + readerText.filterIsInstance().isEmpty() || + readerText.filterIsInstance().isEmpty() + ) { + Log.e(PDF_TAG, "Could not extract text from PDF.") + return emptyList() } Log.i(PDF_TAG, "Successfully finished PDF parsing.") - Resource.Success( - listOf( - ChapterWithText( - chapter = Chapter( - index = 0, - title = title, - startIndex = 0, - endIndex = strings.lastIndex - ), - text = strings - ) - ) - ) + readerText } catch (e: Exception) { e.printStackTrace() - Resource.Error( - UIText.StringResource( - R.string.error_query, - e.message?.take(40)?.trim() ?: "" - ) - ) + emptyList() } } } diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt index 216ed8a9..e21dfbac 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/txt/TxtTextParser.kt @@ -4,12 +4,9 @@ import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.yield -import ua.acclorite.book_story.R +import ua.acclorite.book_story.data.parser.MarkdownParser import ua.acclorite.book_story.data.parser.TextParser -import ua.acclorite.book_story.domain.reader.Chapter -import ua.acclorite.book_story.domain.reader.ChapterWithText -import ua.acclorite.book_story.domain.ui.UIText -import ua.acclorite.book_story.domain.util.Resource +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.presentation.core.util.clearAllMarkdown import java.io.BufferedReader import java.io.File @@ -18,58 +15,59 @@ import javax.inject.Inject private const val TXT_TAG = "TXT Parser" -class TxtTextParser @Inject constructor() : TextParser { +class TxtTextParser @Inject constructor( + private val markdownParser: MarkdownParser +) : TextParser { - override suspend fun parse(file: File): Resource> { + override suspend fun parse(file: File): List { Log.i(TXT_TAG, "Started TXT parsing: ${file.name}.") return try { - val lines = mutableListOf() + val readerText = mutableListOf() + var chapterAdded = false withContext(Dispatchers.IO) { BufferedReader(FileReader(file)).forEachLine { line -> if (line.isNotBlank()) { - lines.add( - line.trim() - ) + when (line) { + "***", "---" -> readerText.add( + ReaderText.Separator + ) + + else -> { + if (!chapterAdded && line.clearAllMarkdown().isNotBlank()) { + readerText.add( + 0, ReaderText.Chapter( + title = line.clearAllMarkdown() + ) + ) + chapterAdded = true + } else readerText.add( + ReaderText.Text( + line = markdownParser.parse(line) + ) + ) + } + } } } } yield() - if (lines.size < 2) { - return Resource.Error(UIText.StringResource(R.string.error_file_empty)) - } - - val title = lines.first().clearAllMarkdown().let { title -> - lines.removeAt(0) - if (title.isBlank()) return@let "Chapter 1" - return@let title + if ( + readerText.filterIsInstance().isEmpty() || + readerText.filterIsInstance().isEmpty() + ) { + Log.e(TXT_TAG, "Could not extract text from TXT.") + return emptyList() } Log.i(TXT_TAG, "Successfully finished TXT parsing.") - Resource.Success( - listOf( - ChapterWithText( - chapter = Chapter( - index = 0, - title = title, - startIndex = 0, - endIndex = lines.lastIndex - ), - text = lines - ) - ) - ) + readerText } catch (e: Exception) { e.printStackTrace() - Resource.Error( - UIText.StringResource( - R.string.error_query, - e.message?.take(40)?.trim() ?: "" - ) - ) + emptyList() } } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt index add1f632..7dfed350 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/repository/BookRepositoryImpl.kt @@ -6,20 +6,17 @@ import android.graphics.BitmapFactory import android.net.Uri import android.provider.MediaStore import android.util.Log -import androidx.compose.ui.text.AnnotatedString import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import ua.acclorite.book_story.data.local.room.BookDao import ua.acclorite.book_story.data.mapper.book.BookMapper import ua.acclorite.book_story.data.parser.FileParser -import ua.acclorite.book_story.data.parser.MarkdownParser import ua.acclorite.book_story.data.parser.TextParser import ua.acclorite.book_story.domain.library.book.Book import ua.acclorite.book_story.domain.library.book.BookWithCover -import ua.acclorite.book_story.domain.reader.ChaptersAndText +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.domain.repository.BookRepository import ua.acclorite.book_story.domain.util.CoverImage -import ua.acclorite.book_story.domain.util.Resource import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream @@ -45,8 +42,7 @@ class BookRepositoryImpl @Inject constructor( private val bookMapper: BookMapper, private val fileParser: FileParser, - private val textParser: TextParser, - private val markdownParser: MarkdownParser + private val textParser: TextParser ) : BookRepository { /** @@ -90,43 +86,31 @@ class BookRepositoryImpl @Inject constructor( } /** - * Loads text from the book. + * Loads text from the book. Already formatted. */ - override suspend fun getBookText(bookId: Int): ChaptersAndText { - if (bookId == -1) return ChaptersAndText(chapters = emptyList(), text = emptyList()) + override suspend fun getBookText(bookId: Int): List { + if (bookId == -1) return emptyList() val book = database.findBookById(bookId) val file = File(book.filePath) if (!file.exists()) { Log.e(GET_TEXT, "File [$bookId] does not exist") - return ChaptersAndText(chapters = emptyList(), text = emptyList()) + return emptyList() } - val parsedText = textParser.parse(file) - if (parsedText is Resource.Error) { - Log.e(GET_TEXT, "Failed to load text: $bookId") - return ChaptersAndText(chapters = emptyList(), text = emptyList()) - } + val readerText = textParser.parse(file) - val chapters = parsedText.data!!.map { it.chapter } - val markdownLines = mutableListOf() - - withContext(Dispatchers.IO) { - for (line in parsedText.data.map { it.text }.flatten()) { - if (line.isNotBlank()) { - markdownLines.add( - markdownParser.parse(line.trim()) - ) - } - } + if ( + readerText.filterIsInstance().isEmpty() || + readerText.filterIsInstance().isEmpty() + ) { + Log.e(GET_TEXT, "Could not load text from [$bookId].") + return emptyList() } Log.i(GET_TEXT, "Successfully loaded text of [$bookId] with markdown.") - return ChaptersAndText( - chapters = chapters, - text = markdownLines - ) + return readerText } /** diff --git a/app/src/main/java/ua/acclorite/book_story/domain/reader/Chapter.kt b/app/src/main/java/ua/acclorite/book_story/domain/reader/Chapter.kt deleted file mode 100644 index 63e3f64f..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/reader/Chapter.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ua.acclorite.book_story.domain.reader - -import android.os.Parcelable -import androidx.compose.runtime.Immutable -import kotlinx.parcelize.Parcelize - -@Parcelize -@Immutable -data class Chapter( - val index: Int = 0, - val title: String, - val startIndex: Int, - val endIndex: Int -) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/reader/ChapterWithText.kt b/app/src/main/java/ua/acclorite/book_story/domain/reader/ChapterWithText.kt deleted file mode 100644 index 5922b322..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/reader/ChapterWithText.kt +++ /dev/null @@ -1,9 +0,0 @@ -package ua.acclorite.book_story.domain.reader - -import androidx.compose.runtime.Immutable - -@Immutable -data class ChapterWithText( - val chapter: Chapter, - val text: List -) \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/reader/ChaptersAndText.kt b/app/src/main/java/ua/acclorite/book_story/domain/reader/ChaptersAndText.kt deleted file mode 100644 index b87053c4..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/reader/ChaptersAndText.kt +++ /dev/null @@ -1,10 +0,0 @@ -package ua.acclorite.book_story.domain.reader - -import androidx.compose.runtime.Immutable -import androidx.compose.ui.text.AnnotatedString - -@Immutable -data class ChaptersAndText( - val chapters: List, - val text: List -) \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/reader/ReaderText.kt b/app/src/main/java/ua/acclorite/book_story/domain/reader/ReaderText.kt new file mode 100644 index 00000000..69be258b --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/domain/reader/ReaderText.kt @@ -0,0 +1,16 @@ +package ua.acclorite.book_story.domain.reader + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.text.AnnotatedString + +@Immutable +sealed class ReaderText { + @Immutable + data class Chapter(val title: String) : ReaderText() + + @Immutable + data class Text(val line: AnnotatedString) : ReaderText() + + @Immutable + data object Separator : ReaderText() +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt index 6ffe724f..efcee448 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt @@ -2,7 +2,7 @@ package ua.acclorite.book_story.domain.repository import ua.acclorite.book_story.domain.library.book.Book import ua.acclorite.book_story.domain.library.book.BookWithCover -import ua.acclorite.book_story.domain.reader.ChaptersAndText +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.domain.util.CoverImage interface BookRepository { @@ -17,7 +17,7 @@ interface BookRepository { suspend fun getBookText( bookId: Int - ): ChaptersAndText + ): List suspend fun insertBook( bookWithCover: BookWithCover diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetText.kt index c0808ee4..aeb17603 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetText.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/book/GetText.kt @@ -1,6 +1,6 @@ package ua.acclorite.book_story.domain.use_case.book -import ua.acclorite.book_story.domain.reader.ChaptersAndText +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.domain.repository.BookRepository import javax.inject.Inject @@ -8,7 +8,7 @@ class GetText @Inject constructor( private val repository: BookRepository ) { - suspend fun execute(bookId: Int): ChaptersAndText { + suspend fun execute(bookId: Int): List { return repository.getBookText(bookId = bookId) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/domain/util/Resource.kt b/app/src/main/java/ua/acclorite/book_story/domain/util/Resource.kt deleted file mode 100644 index 8e02c3ff..00000000 --- a/app/src/main/java/ua/acclorite/book_story/domain/util/Resource.kt +++ /dev/null @@ -1,8 +0,0 @@ -package ua.acclorite.book_story.domain.util - -import ua.acclorite.book_story.domain.ui.UIText - -sealed class Resource(val data: T? = null, val message: UIText? = null) { - class Success(data: T?) : Resource(data) - class Error(message: UIText, data: T? = null) : Resource(data, message) -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/core/util/PrimitiveExtensions.kt b/app/src/main/java/ua/acclorite/book_story/presentation/core/util/PrimitiveExtensions.kt index 294afe92..422eeb1f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/core/util/PrimitiveExtensions.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/core/util/PrimitiveExtensions.kt @@ -18,7 +18,8 @@ fun Float.calculateProgress(digits: Int): String { .dropWhile { it == '-' } } -fun Float.coerceAndPreventNaN(): Float { +fun Float?.coerceAndPreventNaN(): Float { + if (this == null) return 0f if (isNaN()) return 0f return this.coerceIn(0f, 1f) } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderBottomBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderBottomBar.kt index ae1f534d..2d96408a 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderBottomBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderBottomBar.kt @@ -22,13 +22,13 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.domain.reader.Chapter import ua.acclorite.book_story.domain.reader.Checkpoint +import ua.acclorite.book_story.domain.reader.ReaderText +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.util.Direction import ua.acclorite.book_story.presentation.core.components.common.IconButton import ua.acclorite.book_story.presentation.core.util.calculateProgress @@ -40,7 +40,7 @@ import ua.acclorite.book_story.ui.theme.HorizontalExpandingTransition @Composable fun ReaderBottomBar( book: Book, - text: List, + text: List, listState: LazyListState, lockMenu: Boolean, currentChapter: Chapter?, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderChaptersDrawer.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderChaptersDrawer.kt index db9a2534..69bf6ad5 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderChaptersDrawer.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderChaptersDrawer.kt @@ -2,16 +2,18 @@ package ua.acclorite.book_story.presentation.reader import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.reader.Chapter +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter import ua.acclorite.book_story.presentation.core.components.modal_drawer.ModalDrawer import ua.acclorite.book_story.presentation.core.components.modal_drawer.ModalDrawerSelectableItem import ua.acclorite.book_story.presentation.core.components.modal_drawer.ModalDrawerTitleItem @@ -27,9 +29,15 @@ fun ReaderChaptersDrawer( scrollToChapter: (ReaderEvent.OnScrollToChapter) -> Unit, dismissDrawer: (ReaderEvent.OnDismissDrawer) -> Unit ) { + val currentChapterIndex = remember(chapters, currentChapter) { + derivedStateOf { + chapters.indexOf(currentChapter).takeIf { it != -1 } ?: 0 + } + } + ModalDrawer( show = show, - startIndex = currentChapter?.index ?: 0, + startIndex = currentChapterIndex.value, onDismissRequest = { dismissDrawer(ReaderEvent.OnDismissDrawer) }, header = { ModalDrawerTitleItem( @@ -37,9 +45,9 @@ fun ReaderChaptersDrawer( ) } ) { - items(chapters, key = { it.index }) { chapter -> - val selected = rememberSaveable(currentChapter) { - chapter.index == currentChapter?.index + itemsIndexed(chapters, key = { index, _ -> index }) { index, chapter -> + val selected = rememberSaveable(index, currentChapterIndex) { + index == currentChapterIndex.value } ModalDrawerSelectableItem( @@ -47,7 +55,7 @@ fun ReaderChaptersDrawer( onClick = { scrollToChapter( ReaderEvent.OnScrollToChapter( - chapterStartIndex = chapter.startIndex + chapter = chapter ) ) dismissDrawer(ReaderEvent.OnDismissDrawer) diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderContent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderContent.kt index 9b84099c..b331bb9f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderContent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderContent.kt @@ -3,17 +3,18 @@ package ua.acclorite.book_story.presentation.reader import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.domain.reader.Chapter import ua.acclorite.book_story.domain.reader.Checkpoint import ua.acclorite.book_story.domain.reader.FontWithName import ua.acclorite.book_story.domain.reader.ReaderHorizontalGesture +import ua.acclorite.book_story.domain.reader.ReaderText +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.reader.ReaderTextAlignment import ua.acclorite.book_story.domain.ui.UIText import ua.acclorite.book_story.domain.util.BottomSheet @@ -24,7 +25,7 @@ import ua.acclorite.book_story.ui.settings.SettingsEvent @Composable fun ReaderContent( book: Book, - text: List, + text: List, bottomSheet: BottomSheet?, drawer: Drawer?, listState: LazyListState, @@ -40,7 +41,6 @@ fun ReaderContent( checkpoint: Checkpoint, showMenu: Boolean, lockMenu: Boolean, - chapters: Map, contentPadding: PaddingValues, verticalPadding: Dp, horizontalGesture: ReaderHorizontalGesture, @@ -102,7 +102,6 @@ fun ReaderContent( checkpoint = checkpoint, showMenu = showMenu, lockMenu = lockMenu, - chapters = chapters, contentPadding = contentPadding, verticalPadding = verticalPadding, horizontalGesture = horizontalGesture, @@ -141,7 +140,7 @@ fun ReaderContent( ReaderDrawer( drawer = drawer, - chapters = chapters.values.toList(), + chapters = remember(text) { text.filterIsInstance() }, currentChapter = currentChapter, currentChapterProgress = currentChapterProgress, scrollToChapter = scrollToChapter, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderDrawer.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderDrawer.kt index 2174df19..f84a3b2b 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderDrawer.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderDrawer.kt @@ -1,7 +1,7 @@ package ua.acclorite.book_story.presentation.reader import androidx.compose.runtime.Composable -import ua.acclorite.book_story.domain.reader.Chapter +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.util.Drawer import ua.acclorite.book_story.ui.reader.ReaderEvent import ua.acclorite.book_story.ui.reader.ReaderScreen diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayout.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayout.kt index 55d522a9..823c66bd 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayout.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayout.kt @@ -14,16 +14,15 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.coerceAtLeast import androidx.compose.ui.unit.dp import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.reader.Chapter import ua.acclorite.book_story.domain.reader.FontWithName import ua.acclorite.book_story.domain.reader.ReaderHorizontalGesture +import ua.acclorite.book_story.domain.reader.ReaderText import ua.acclorite.book_story.domain.reader.ReaderTextAlignment import ua.acclorite.book_story.presentation.core.components.common.LazyColumnWithScrollbar import ua.acclorite.book_story.presentation.core.components.common.SelectionContainer @@ -34,8 +33,7 @@ import ua.acclorite.book_story.ui.reader.ReaderEvent @Composable fun ReaderLayout( - text: List, - chapters: Map, + text: List, listState: LazyListState, contentPadding: PaddingValues, verticalPadding: Dp, @@ -148,17 +146,11 @@ fun ReaderLayout( ) { itemsIndexed( text, key = { index, _ -> index } - ) { index, line -> - ReaderLayoutChapter( - chapter = chapters[index], // Shows only when matching startIndex of chapter. - fontColor = fontColor, - sidePadding = sidePadding - ) - - ReaderLayoutParagraph( + ) { index, textEntry -> + ReaderLayoutText( activity = activity, showMenu = showMenu, - line = line, + textEntry = textEntry, fontFamily = fontFamily, fontColor = fontColor, lineHeight = lineHeight, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutParagraph.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutParagraph.kt deleted file mode 100644 index 4ba4700c..00000000 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutParagraph.kt +++ /dev/null @@ -1,120 +0,0 @@ -package ua.acclorite.book_story.presentation.reader - -import androidx.activity.ComponentActivity -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyItemScope -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.text.BasicText -import androidx.compose.material3.HorizontalDivider -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.style.LineBreak -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextIndent -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.domain.reader.FontWithName -import ua.acclorite.book_story.domain.reader.ReaderTextAlignment -import ua.acclorite.book_story.presentation.core.util.noRippleClickable -import ua.acclorite.book_story.ui.reader.ReaderEvent - -@Composable -fun LazyItemScope.ReaderLayoutParagraph( - activity: ComponentActivity, - showMenu: Boolean, - line: AnnotatedString, - fontFamily: FontWithName, - fontColor: Color, - lineHeight: TextUnit, - fontStyle: FontStyle, - textAlignment: ReaderTextAlignment, - fontSize: TextUnit, - letterSpacing: TextUnit, - sidePadding: Dp, - paragraphIndentation: TextUnit, - fullscreenMode: Boolean, - doubleClickTranslation: Boolean, - toolbarHidden: Boolean, - openTranslator: (ReaderEvent.OnOpenTranslator) -> Unit, - menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit -) { - Column( - modifier = Modifier - .animateItem(fadeInSpec = null, fadeOutSpec = null) - .fillMaxWidth() - .padding(horizontal = sidePadding), - verticalArrangement = Arrangement.Center, - horizontalAlignment = when (textAlignment) { - ReaderTextAlignment.START, ReaderTextAlignment.JUSTIFY -> Alignment.Start - ReaderTextAlignment.CENTER -> Alignment.CenterHorizontally - ReaderTextAlignment.END -> Alignment.End - } - ) { - when (line.text) { - "---" -> { - HorizontalDivider( - thickness = 3.dp, - modifier = Modifier.clip(CircleShape), - color = fontColor.copy(0.3f) - ) - } - - else -> { - BasicText( - text = line, - modifier = Modifier.then( - if (doubleClickTranslation && toolbarHidden) { - Modifier.noRippleClickable( - onDoubleClick = { - openTranslator( - ReaderEvent.OnOpenTranslator( - textToTranslate = line.text, - translateWholeParagraph = true, - activity = activity - ) - ) - }, - onClick = { - menuVisibility( - ReaderEvent.OnMenuVisibility( - show = !showMenu, - fullscreenMode = fullscreenMode, - saveCheckpoint = true, - activity = activity - ) - ) - } - ) - } else Modifier - ), - style = TextStyle( - fontFamily = fontFamily.font, - textAlign = when (textAlignment) { - ReaderTextAlignment.START -> TextAlign.Start - ReaderTextAlignment.JUSTIFY -> TextAlign.Justify - ReaderTextAlignment.CENTER -> TextAlign.Center - ReaderTextAlignment.END -> TextAlign.End - }, - textIndent = TextIndent(firstLine = paragraphIndentation), - fontStyle = fontStyle, - letterSpacing = letterSpacing, - fontSize = fontSize, - lineHeight = lineHeight, - color = fontColor, - lineBreak = LineBreak.Paragraph - ) - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutText.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutText.kt new file mode 100644 index 00000000..9e020c3d --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutText.kt @@ -0,0 +1,73 @@ +package ua.acclorite.book_story.presentation.reader + +import androidx.activity.ComponentActivity +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import ua.acclorite.book_story.domain.reader.FontWithName +import ua.acclorite.book_story.domain.reader.ReaderText +import ua.acclorite.book_story.domain.reader.ReaderTextAlignment +import ua.acclorite.book_story.ui.reader.ReaderEvent + +@Composable +fun LazyItemScope.ReaderLayoutText( + activity: ComponentActivity, + showMenu: Boolean, + textEntry: ReaderText, + fontFamily: FontWithName, + fontColor: Color, + lineHeight: TextUnit, + fontStyle: FontStyle, + textAlignment: ReaderTextAlignment, + fontSize: TextUnit, + letterSpacing: TextUnit, + sidePadding: Dp, + paragraphIndentation: TextUnit, + fullscreenMode: Boolean, + doubleClickTranslation: Boolean, + toolbarHidden: Boolean, + openTranslator: (ReaderEvent.OnOpenTranslator) -> Unit, + menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit +) { + when (textEntry) { + is ReaderText.Separator -> { + ReaderLayoutTextSeparator( + sidePadding = sidePadding, + fontColor = fontColor + ) + } + + is ReaderText.Chapter -> { + ReaderLayoutTextChapter( + chapter = textEntry, + fontColor = fontColor, + sidePadding = sidePadding + ) + } + + is ReaderText.Text -> { + ReaderLayoutTextParagraph( + paragraph = textEntry, + activity = activity, + showMenu = showMenu, + fontFamily = fontFamily, + fontColor = fontColor, + lineHeight = lineHeight, + fontStyle = fontStyle, + textAlignment = textAlignment, + fontSize = fontSize, + letterSpacing = letterSpacing, + sidePadding = sidePadding, + paragraphIndentation = paragraphIndentation, + fullscreenMode = fullscreenMode, + doubleClickTranslation = doubleClickTranslation, + toolbarHidden = toolbarHidden, + openTranslator = openTranslator, + menuVisibility = menuVisibility + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutChapter.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextChapter.kt similarity index 51% rename from app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutChapter.kt rename to app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextChapter.kt index c6daa9f2..181c8f9c 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutChapter.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextChapter.kt @@ -11,24 +11,22 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import ua.acclorite.book_story.domain.reader.Chapter +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter @Composable -fun ReaderLayoutChapter( - chapter: Chapter?, +fun ReaderLayoutTextChapter( + chapter: Chapter, fontColor: Color, sidePadding: Dp ) { - chapter?.let { - Spacer(modifier = Modifier.height(22.dp)) - Text( - text = chapter.title, - style = MaterialTheme.typography.headlineMedium, - color = fontColor, - modifier = Modifier.padding(horizontal = sidePadding) - ) - Spacer(modifier = Modifier.height(16.dp)) - HorizontalDivider(color = fontColor.copy(0.4f)) - Spacer(modifier = Modifier.height(16.dp)) - } + Spacer(modifier = Modifier.height(22.dp)) + Text( + text = chapter.title, + style = MaterialTheme.typography.headlineMedium, + color = fontColor, + modifier = Modifier.padding(horizontal = sidePadding) + ) + Spacer(modifier = Modifier.height(16.dp)) + HorizontalDivider(color = fontColor.copy(0.4f)) + Spacer(modifier = Modifier.height(16.dp)) } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextParagraph.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextParagraph.kt new file mode 100644 index 00000000..d1a0f282 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextParagraph.kt @@ -0,0 +1,104 @@ +package ua.acclorite.book_story.presentation.reader + +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.text.BasicText +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.style.LineBreak +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextIndent +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import ua.acclorite.book_story.domain.reader.FontWithName +import ua.acclorite.book_story.domain.reader.ReaderText.Text +import ua.acclorite.book_story.domain.reader.ReaderTextAlignment +import ua.acclorite.book_story.presentation.core.util.noRippleClickable +import ua.acclorite.book_story.ui.reader.ReaderEvent + +@Composable +fun LazyItemScope.ReaderLayoutTextParagraph( + paragraph: Text, + activity: ComponentActivity, + showMenu: Boolean, + fontFamily: FontWithName, + fontColor: Color, + lineHeight: TextUnit, + fontStyle: FontStyle, + textAlignment: ReaderTextAlignment, + fontSize: TextUnit, + letterSpacing: TextUnit, + sidePadding: Dp, + paragraphIndentation: TextUnit, + fullscreenMode: Boolean, + doubleClickTranslation: Boolean, + toolbarHidden: Boolean, + openTranslator: (ReaderEvent.OnOpenTranslator) -> Unit, + menuVisibility: (ReaderEvent.OnMenuVisibility) -> Unit +) { + Column( + modifier = Modifier + .animateItem(fadeInSpec = null, fadeOutSpec = null) + .fillMaxWidth() + .padding(horizontal = sidePadding), + verticalArrangement = Arrangement.Center, + horizontalAlignment = when (textAlignment) { + ReaderTextAlignment.START, ReaderTextAlignment.JUSTIFY -> Alignment.Start + ReaderTextAlignment.CENTER -> Alignment.CenterHorizontally + ReaderTextAlignment.END -> Alignment.End + } + ) { + BasicText( + text = paragraph.line, + modifier = Modifier.then( + if (doubleClickTranslation && toolbarHidden) { + Modifier.noRippleClickable( + onDoubleClick = { + openTranslator( + ReaderEvent.OnOpenTranslator( + textToTranslate = paragraph.line.text, + translateWholeParagraph = true, + activity = activity + ) + ) + }, + onClick = { + menuVisibility( + ReaderEvent.OnMenuVisibility( + show = !showMenu, + fullscreenMode = fullscreenMode, + saveCheckpoint = true, + activity = activity + ) + ) + } + ) + } else Modifier + ), + style = TextStyle( + fontFamily = fontFamily.font, + textAlign = when (textAlignment) { + ReaderTextAlignment.START -> TextAlign.Start + ReaderTextAlignment.JUSTIFY -> TextAlign.Justify + ReaderTextAlignment.CENTER -> TextAlign.Center + ReaderTextAlignment.END -> TextAlign.End + }, + textIndent = TextIndent(firstLine = paragraphIndentation), + fontStyle = fontStyle, + letterSpacing = letterSpacing, + fontSize = fontSize, + lineHeight = lineHeight, + color = fontColor, + lineBreak = LineBreak.Paragraph + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextSeparator.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextSeparator.kt new file mode 100644 index 00000000..10f40d49 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderLayoutTextSeparator.kt @@ -0,0 +1,25 @@ +package ua.acclorite.book_story.presentation.reader + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun ReaderLayoutTextSeparator( + sidePadding: Dp, + fontColor: Color +) { + HorizontalDivider( + thickness = 3.dp, + modifier = Modifier + .padding(horizontal = sidePadding) + .clip(CircleShape), + color = fontColor.copy(0.3f) + ) +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderScaffold.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderScaffold.kt index 75914d6f..107178a6 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderScaffold.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderScaffold.kt @@ -14,15 +14,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.domain.reader.Chapter import ua.acclorite.book_story.domain.reader.Checkpoint import ua.acclorite.book_story.domain.reader.FontWithName import ua.acclorite.book_story.domain.reader.ReaderHorizontalGesture +import ua.acclorite.book_story.domain.reader.ReaderText +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.reader.ReaderTextAlignment import ua.acclorite.book_story.domain.ui.UIText import ua.acclorite.book_story.presentation.core.components.common.AnimatedVisibility @@ -33,7 +33,7 @@ import ua.acclorite.book_story.ui.settings.SettingsEvent @Composable fun ReaderScaffold( book: Book, - text: List, + text: List, listState: LazyListState, currentChapter: Chapter?, nestedScrollConnection: NestedScrollConnection, @@ -47,7 +47,6 @@ fun ReaderScaffold( checkpoint: Checkpoint, showMenu: Boolean, lockMenu: Boolean, - chapters: Map, contentPadding: PaddingValues, verticalPadding: Dp, horizontalGesture: ReaderHorizontalGesture, @@ -136,7 +135,6 @@ fun ReaderScaffold( ) { ReaderLayout( text = text, - chapters = chapters, listState = listState, contentPadding = contentPadding, verticalPadding = verticalPadding, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderTopBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderTopBar.kt index 0468c26d..4b2217fb 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderTopBar.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/reader/ReaderTopBar.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.domain.reader.Chapter +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter import ua.acclorite.book_story.presentation.core.components.common.IconButton import ua.acclorite.book_story.presentation.core.util.LocalActivity import ua.acclorite.book_story.presentation.core.util.noRippleClickable diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderEvent.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderEvent.kt index babed6de..359b050a 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderEvent.kt @@ -3,6 +3,7 @@ package ua.acclorite.book_story.ui.reader import android.content.Context import androidx.activity.ComponentActivity import androidx.compose.runtime.Immutable +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter @Immutable sealed class ReaderEvent { @@ -25,7 +26,7 @@ sealed class ReaderEvent { ) : ReaderEvent() data class OnScrollToChapter( - val chapterStartIndex: Int + val chapter: Chapter ) : ReaderEvent() data class OnScroll( diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt index ed2be3d3..bc0292c2 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt @@ -3,6 +3,7 @@ package ua.acclorite.book_story.ui.reader import android.app.SearchManager import android.content.Intent import android.net.Uri +import android.util.Log import androidx.activity.ComponentActivity import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.snapshotFlow @@ -28,8 +29,8 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R -import ua.acclorite.book_story.domain.reader.Chapter import ua.acclorite.book_story.domain.reader.Checkpoint +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.ui.UIText import ua.acclorite.book_story.domain.use_case.book.GetBookById import ua.acclorite.book_story.domain.use_case.book.GetText @@ -44,6 +45,8 @@ import ua.acclorite.book_story.ui.library.LibraryScreen import javax.inject.Inject import kotlin.math.roundToInt +private const val READER = "READER, MODEL" + @HiltViewModel class ReaderModel @Inject constructor( private val getBookById: GetBookById, @@ -67,20 +70,19 @@ class ReaderModel @Inject constructor( when (event) { is ReaderEvent.OnLoadText -> { launch(Dispatchers.IO) { - val chaptersAndText = getText.execute(_state.value.book.id) + val text = getText.execute(_state.value.book.id) yield() - if (chaptersAndText.text.isEmpty()) { + if (text.isEmpty()) { _state.update { it.copy( isLoading = false, - errorMessage = UIText.StringResource(R.string.error_no_text) //todo rename + errorMessage = UIText.StringResource(R.string.error_no_text) ) } return@launch } - yield() val lastOpened = getLatestHistory.execute(_state.value.book.id)?.time yield() @@ -89,8 +91,7 @@ class ReaderModel @Inject constructor( book = it.book.copy( lastOpened = lastOpened ), - chapters = chaptersAndText.chapters, - text = chaptersAndText.text + text = text ) } @@ -177,17 +178,23 @@ class ReaderModel @Inject constructor( is ReaderEvent.OnScrollToChapter -> { launch { - _state.value.listState.requestScrollToItem( - event.chapterStartIndex - ) - updateChapter(index = event.chapterStartIndex) - onEvent( - ReaderEvent.OnChangeProgress( - progress = calculateProgress(event.chapterStartIndex), - firstVisibleItemIndex = event.chapterStartIndex, - firstVisibleItemOffset = 0 + _state.value.apply { + val chapterIndex = text.indexOf(event.chapter).takeIf { it != -1 } + + if (chapterIndex == null) { + return@launch + } + + listState.requestScrollToItem(chapterIndex) + updateChapter(index = chapterIndex) + onEvent( + ReaderEvent.OnChangeProgress( + progress = calculateProgress(chapterIndex), + firstVisibleItemIndex = chapterIndex, + firstVisibleItemOffset = 0 + ) ) - ) + } } } @@ -195,7 +202,6 @@ class ReaderModel @Inject constructor( scrollJob?.cancel() scrollJob = launch { delay(300) - yield() val scrollTo = (_state.value.text.size * event.progress).roundToInt() @@ -211,6 +217,7 @@ class ReaderModel @Inject constructor( checkpoint.offset ) + updateChapter(checkpoint.index) onEvent( ReaderEvent.OnChangeProgress( progress = calculateProgress(checkpoint.index), @@ -234,9 +241,9 @@ class ReaderModel @Inject constructor( _state.value.listState.apply { if ( - _state.value.isLoading - || layoutInfo.totalItemsCount < 1 - || _state.value.text.isEmpty() + _state.value.isLoading || + layoutInfo.totalItemsCount < 1 || + _state.value.text.isEmpty() ) return@apply _state.update { @@ -284,6 +291,8 @@ class ReaderModel @Inject constructor( "translate: ${event.textToTranslate.trim()}" ) + yield() + translatorIntent.launchActivity( activity = event.activity, createChooser = !event.translateWholeParagraph, @@ -320,6 +329,8 @@ class ReaderModel @Inject constructor( event.textToShare.trim() ) + yield() + shareIntent.launchActivity( activity = event.activity, createChooser = true, @@ -345,6 +356,8 @@ class ReaderModel @Inject constructor( event.textToSearch ) + yield() + browserIntent.launchActivity( activity = event.activity, success = { @@ -376,6 +389,8 @@ class ReaderModel @Inject constructor( val text = event.textToDefine.trim().replace(" ", "+") browserIntent.data = Uri.parse("https://www.onelook.com/?w=$text") + yield() + dictionaryIntent.launchActivity( activity = event.activity, createChooser = true, @@ -485,6 +500,10 @@ class ReaderModel @Inject constructor( if (progress == _state.value.book.progress) return@collectLatest val (currentChapter, currentChapterProgress) = calculateCurrentChapter(index) + Log.i( + READER, + "Changed progress|currentChapter: $progress; ${currentChapter?.title}" + ) _state.update { it.copy( book = it.book.copy( @@ -509,6 +528,11 @@ class ReaderModel @Inject constructor( viewModelScope.launch { val (currentChapter, currentChapterProgress) = calculateCurrentChapter(index) _state.update { + Log.i( + READER, + "Changed currentChapter|currentChapterProgress:" + + " ${currentChapter?.title}($currentChapterProgress)" + ) it.copy( currentChapter = currentChapter, currentChapterProgress = currentChapterProgress @@ -518,20 +542,33 @@ class ReaderModel @Inject constructor( } private fun calculateCurrentChapter(index: Int): Pair { - val currentChapter = _state.value.chapters.find { chapter -> - index in chapter.startIndex..chapter.endIndex - } - val currentChapterProgress = currentChapter.run { - if (this == null) return@run 0f + val currentChapter = findCurrentChapter(index) + val currentChapterProgress = currentChapter?.let { chapter -> + _state.value.text.run { + val startIndex = (indexOf(chapter) + 1).coerceAtMost(count()) + val endIndex = (indexOfFirst { + it is Chapter && indexOf(it) > startIndex + }.takeIf { it != -1 }?.minus(1)) ?: lastIndex - val currentIndex = index - startIndex - val endIndex = endIndex - startIndex - (currentIndex / endIndex.toFloat()) + val currentIndexInChapter = index - startIndex + val chapterLength = endIndex - startIndex + (currentIndexInChapter / chapterLength.toFloat()) + } }.coerceAndPreventNaN() return currentChapter to currentChapterProgress } + private fun findCurrentChapter(index: Int): Chapter? { + for (textIndex in index downTo 0) { + val readerText = _state.value.text[textIndex] + if (readerText is Chapter) { + return readerText + } + } + return null + } + private fun calculateProgress(firstVisibleItemIndex: Int? = null): Float { return _state.value.run { if ( diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScreen.kt index bc24ba6b..63f60a3c 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderScreen.kt @@ -34,7 +34,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.parcelize.Parcelize import ua.acclorite.book_story.domain.navigator.Screen -import ua.acclorite.book_story.domain.reader.Chapter import ua.acclorite.book_story.domain.reader.ReaderTextAlignment import ua.acclorite.book_story.presentation.core.constants.Constants import ua.acclorite.book_story.presentation.core.constants.provideFonts @@ -224,14 +223,6 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable { (mainState.value.bottomBarPadding * 4f).dp } - val chapters = remember(state.value.chapters) { - val chapters = mutableMapOf() - state.value.chapters.forEach { - chapters[it.startIndex] = it - } - chapters - } - LaunchedEffect(Unit) { screenModel.init( bookId = bookId, @@ -313,7 +304,6 @@ data class ReaderScreen(val bookId: Int) : Screen, Parcelable { checkpoint = state.value.checkpoint, showMenu = state.value.showMenu, lockMenu = state.value.lockMenu, - chapters = chapters, contentPadding = contentPadding, verticalPadding = verticalPadding, horizontalGesture = mainState.value.horizontalGesture, diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderState.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderState.kt index ef8fcc56..972d8e54 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderState.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderState.kt @@ -2,10 +2,10 @@ package ua.acclorite.book_story.ui.reader import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Immutable -import androidx.compose.ui.text.AnnotatedString import ua.acclorite.book_story.domain.library.book.Book -import ua.acclorite.book_story.domain.reader.Chapter import ua.acclorite.book_story.domain.reader.Checkpoint +import ua.acclorite.book_story.domain.reader.ReaderText +import ua.acclorite.book_story.domain.reader.ReaderText.Chapter import ua.acclorite.book_story.domain.ui.UIText import ua.acclorite.book_story.domain.util.BottomSheet import ua.acclorite.book_story.domain.util.Drawer @@ -15,8 +15,7 @@ import ua.acclorite.book_story.presentation.core.constants.provideEmptyBook @Immutable data class ReaderState( val book: Book = Constants.provideEmptyBook(), - val chapters: List = emptyList(), - val text: List = emptyList(), + val text: List = emptyList(), val listState: LazyListState = LazyListState(), val currentChapter: Chapter? = null,