From b8721b6ad725dd52f75f5af49e74d6a4b209272a Mon Sep 17 00:00:00 2001
From: Acclorite <140836141+Acclorite@users.noreply.github.com>
Date: Tue, 1 Oct 2024 15:10:44 +0300
Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Markdown=20support=20in=20Reader?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Added Markdown support: Bold, Italic, Links and Section separator (hr)
* Added new font variations (Bold & Bold Italic)
* Improve text update (moved to repository & logged)
Resolves: #93
---
app/build.gradle.kts | 4 +
.../acclorite/book_story/data/di/AppModule.kt | 28 ++++
.../book_story/data/parser/DocumentParser.kt | 61 +++++----
.../book_story/data/parser/MarkdownParser.kt | 92 +++++++++++++
.../data/parser/epub/EpubTextParser.kt | 2 +-
.../data/repository/BookRepositoryImpl.kt | 125 ++++++++++++++---
.../domain/repository/BookRepository.kt | 9 +-
.../domain/use_case/CheckTextForUpdate.kt | 13 ++
.../book_story/domain/use_case/GetText.kt | 3 +-
.../book_story/domain/use_case/ParseText.kt | 14 --
.../presentation/core/constants/Constants.kt | 88 ++++++++++--
.../presentation/core/util/Extensions.kt | 3 +-
.../book_info/data/BookInfoViewModel.kt | 128 ++++++------------
.../screens/reader/ReaderScreen.kt | 1 -
.../reader/components/ReaderTextParagraph.kt | 126 +++++++++--------
.../screens/reader/data/ReaderState.kt | 3 +-
.../screens/reader/data/ReaderViewModel.kt | 79 ++++-------
app/src/main/res/font/arimo_medium.ttf | Bin 0 -> 320664 bytes
app/src/main/res/font/arimo_medium_italic.ttf | Bin 0 -> 344472 bytes
app/src/main/res/font/garamond_medium.ttf | Bin 0 -> 563744 bytes
.../main/res/font/garamond_medium_italic.ttf | Bin 0 -> 496160 bytes
app/src/main/res/font/garamond_regular.ttf | Bin 560884 -> 560876 bytes
.../main/res/font/garamond_regular_italic.ttf | Bin 493056 -> 493040 bytes
app/src/main/res/font/jost_medium.ttf | Bin 0 -> 61652 bytes
app/src/main/res/font/jost_medium_italic.ttf | Bin 0 -> 66160 bytes
app/src/main/res/font/lora_medium.ttf | Bin 0 -> 134004 bytes
app/src/main/res/font/lora_medium_italic.ttf | Bin 0 -> 140264 bytes
app/src/main/res/font/merriweather_medium.ttf | Bin 0 -> 142040 bytes
.../res/font/merriweather_medium_italic.ttf | Bin 0 -> 143832 bytes
app/src/main/res/font/montserrat_medium.ttf | Bin 0 -> 197756 bytes
.../res/font/montserrat_medium_italic.ttf | Bin 0 -> 202432 bytes
app/src/main/res/font/mulish_medium.ttf | Bin 0 -> 106568 bytes
.../main/res/font/mulish_medium_italic.ttf | Bin 0 -> 110160 bytes
app/src/main/res/font/notosans_medium.ttf | Bin 0 -> 583016 bytes
.../main/res/font/notosans_medium_italic.ttf | Bin 0 -> 597348 bytes
app/src/main/res/font/notoserif_medium.ttf | Bin 0 -> 516284 bytes
.../main/res/font/notoserif_medium_italic.ttf | Bin 0 -> 536256 bytes
app/src/main/res/font/nunito_medium.ttf | Bin 0 -> 132304 bytes
.../main/res/font/nunito_medium_italic.ttf | Bin 0 -> 135588 bytes
app/src/main/res/font/opensans_medium.ttf | Bin 0 -> 130976 bytes
.../main/res/font/opensans_medium_italic.ttf | Bin 0 -> 136796 bytes
app/src/main/res/font/raleway_medium.ttf | Bin 0 -> 164128 bytes
.../main/res/font/raleway_medium_italic.ttf | Bin 0 -> 162328 bytes
app/src/main/res/font/roboto_medium.ttf | Bin 0 -> 168644 bytes
.../main/res/font/roboto_medium_italic.ttf | Bin 0 -> 173416 bytes
app/src/main/res/font/robotoserif_medium.ttf | Bin 0 -> 250164 bytes
.../res/font/robotoserif_medium_italic.ttf | Bin 0 -> 252284 bytes
app/src/main/res/font/robotoslab_medium.ttf | Bin 0 -> 127036 bytes
48 files changed, 503 insertions(+), 276 deletions(-)
create mode 100644 app/src/main/java/ua/acclorite/book_story/data/parser/MarkdownParser.kt
create mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/CheckTextForUpdate.kt
delete mode 100644 app/src/main/java/ua/acclorite/book_story/domain/use_case/ParseText.kt
create mode 100644 app/src/main/res/font/arimo_medium.ttf
create mode 100644 app/src/main/res/font/arimo_medium_italic.ttf
create mode 100644 app/src/main/res/font/garamond_medium.ttf
create mode 100644 app/src/main/res/font/garamond_medium_italic.ttf
create mode 100644 app/src/main/res/font/jost_medium.ttf
create mode 100644 app/src/main/res/font/jost_medium_italic.ttf
create mode 100644 app/src/main/res/font/lora_medium.ttf
create mode 100644 app/src/main/res/font/lora_medium_italic.ttf
create mode 100644 app/src/main/res/font/merriweather_medium.ttf
create mode 100644 app/src/main/res/font/merriweather_medium_italic.ttf
create mode 100644 app/src/main/res/font/montserrat_medium.ttf
create mode 100644 app/src/main/res/font/montserrat_medium_italic.ttf
create mode 100644 app/src/main/res/font/mulish_medium.ttf
create mode 100644 app/src/main/res/font/mulish_medium_italic.ttf
create mode 100644 app/src/main/res/font/notosans_medium.ttf
create mode 100644 app/src/main/res/font/notosans_medium_italic.ttf
create mode 100644 app/src/main/res/font/notoserif_medium.ttf
create mode 100644 app/src/main/res/font/notoserif_medium_italic.ttf
create mode 100644 app/src/main/res/font/nunito_medium.ttf
create mode 100644 app/src/main/res/font/nunito_medium_italic.ttf
create mode 100644 app/src/main/res/font/opensans_medium.ttf
create mode 100644 app/src/main/res/font/opensans_medium_italic.ttf
create mode 100644 app/src/main/res/font/raleway_medium.ttf
create mode 100644 app/src/main/res/font/raleway_medium_italic.ttf
create mode 100644 app/src/main/res/font/roboto_medium.ttf
create mode 100644 app/src/main/res/font/roboto_medium_italic.ttf
create mode 100644 app/src/main/res/font/robotoserif_medium.ttf
create mode 100644 app/src/main/res/font/robotoserif_medium_italic.ttf
create mode 100644 app/src/main/res/font/robotoslab_medium.ttf
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index e488eaf9..acb860c1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -168,4 +168,8 @@ dependencies {
// Gson
implementation("com.google.code.gson:gson:2.11.0")
+
+ // Markdown
+ implementation("org.commonmark:commonmark:0.23.0")
+ implementation("org.commonmark:commonmark-ext-autolink:0.23.0")
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt b/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt
index cee3b25f..5ab230dc 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/di/AppModule.kt
@@ -8,6 +8,14 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
+import org.commonmark.ext.autolink.AutolinkExtension
+import org.commonmark.node.BlockQuote
+import org.commonmark.node.FencedCodeBlock
+import org.commonmark.node.Heading
+import org.commonmark.node.HtmlBlock
+import org.commonmark.node.IndentedCodeBlock
+import org.commonmark.node.ThematicBreak
+import org.commonmark.parser.Parser
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import ua.acclorite.book_story.data.local.room.BookDao
@@ -33,6 +41,26 @@ object AppModule {
.create(GithubAPI::class.java)
}
+ @Provides
+ @Singleton
+ fun provideCommonmarkParser(): Parser {
+ return Parser
+ .builder()
+ .extensions(listOf(AutolinkExtension.create()))
+ .enabledBlockTypes(
+ setOf(
+ Heading::class.java,
+ HtmlBlock::class.java,
+ ThematicBreak::class.java,
+ BlockQuote::class.java,
+ FencedCodeBlock::class.java,
+ IndentedCodeBlock::class.java,
+ ThematicBreak::class.java
+ )
+ )
+ .build()
+ }
+
@Provides
@Singleton
fun provideBookDao(app: Application): BookDao {
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/DocumentParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/DocumentParser.kt
index 9b0a0e97..f3d1c3fd 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/DocumentParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/DocumentParser.kt
@@ -2,44 +2,59 @@ package ua.acclorite.book_story.data.parser
import kotlinx.coroutines.yield
import org.jsoup.nodes.Document
+import ua.acclorite.book_story.presentation.core.util.clearMarkdown
import javax.inject.Inject
class DocumentParser @Inject constructor() {
/**
* Parses document to get it's text.
+ * Fixes issues such as manual line breaking in
.
+ * Applies Markdown to the text: Bold(**), Italic(_), Section separator(---), and Links(a > href).
*
- * @return Parsed text line by line.
+ * @return Parsed text line by line with Markdown(all lines are not blank).
*/
suspend fun Document.parseDocument(): List {
val lines = mutableListOf()
yield()
- body()
- .select("p")
- .apply {
- forEach { element ->
- yield()
-
- val cleanedText = element.html().replace(Regex("\\n+"), " ")
- element.html(cleanedText)
- }
-
- append("\n")
- }
-
- yield()
-
- body()
- .wholeText()
- .lines()
- .forEach { line ->
+ body().apply {
+ // Remove manual line breaks from all
+ select("p").forEach { element ->
yield()
- if (line.isNotBlank()) {
- lines.add(line.trim())
- }
+ element.html(element.html().replace(Regex("\\n+"), " "))
+ element.append("\n")
}
+ // Markdown
+ select("hr").append("\n---\n")
+ select("b").append("**").prepend("**")
+ select("h1").append("**").prepend("**")
+ select("h2").append("**").prepend("**")
+ select("h3").append("**").prepend("**")
+ select("strong").append("**").prepend("**")
+ select("em").append("_").prepend("_")
+ select("a").forEach { element ->
+ val link = element.attr("href")
+ if (!link.startsWith("http") || element.wholeText().isBlank()) return@forEach
+
+ element.prepend("[")
+ element.append("](${element.attr("href")})")
+ }
+ }.wholeText().lines().forEach { line ->
+ yield()
+
+ val formattedLine = line.replace(
+ Regex("""\*\*\s*(.*?)\s*\*\*"""), "**$1**"
+ ).replace(
+ Regex("""_\s*(.*?)\s*_"""), "_$1_"
+ ).trim()
+
+ if (formattedLine.clearMarkdown().isNotBlank()) {
+ lines.add(formattedLine)
+ }
+ }
+
yield()
return lines
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/MarkdownParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/MarkdownParser.kt
new file mode 100644
index 00000000..ecc03c30
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/MarkdownParser.kt
@@ -0,0 +1,92 @@
+package ua.acclorite.book_story.data.parser
+
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.withLink
+import androidx.compose.ui.text.withStyle
+import org.commonmark.node.Emphasis
+import org.commonmark.node.Heading
+import org.commonmark.node.Link
+import org.commonmark.node.Node
+import org.commonmark.node.StrongEmphasis
+import org.commonmark.node.Text
+import org.commonmark.parser.Parser
+import ua.acclorite.book_story.presentation.core.util.clearMarkdown
+import javax.inject.Inject
+
+class MarkdownParser @Inject constructor(
+ private val commonmarkParser: Parser
+) {
+ /**
+ * Parses markdown text to [androidx.compose.ui.text.AnnotatedString].
+ *
+ * @return Parsed annotated string.
+ */
+ fun parse(markdown: String): AnnotatedString {
+ return try {
+ val annotatedString = buildAnnotatedString {
+ parseNode(commonmarkParser.parse(markdown))
+ }.ifBlank { buildAnnotatedString { append(markdown) } }
+ .trim() as AnnotatedString
+
+ annotatedString
+ } catch (e: Exception) {
+ e.printStackTrace()
+ buildAnnotatedString { append(markdown) }
+ }
+ }
+
+ /**
+ * Parses [Node].
+ * Appends text and applies styles to the target [AnnotatedString.Builder].
+ */
+ private fun AnnotatedString.Builder.parseNode(node: Node) {
+ when (node) {
+ is Heading, is StrongEmphasis -> {
+ withStyle(SpanStyle(fontWeight = FontWeight.Medium)) {
+ parseChildren(node)
+ }
+ }
+
+ is Emphasis -> {
+ withStyle(SpanStyle(fontStyle = FontStyle.Italic)) {
+ parseChildren(node)
+ }
+ }
+
+ is Link -> {
+ withLink(
+ LinkAnnotation.Url(
+ node.destination,
+ styles = TextLinkStyles(style = SpanStyle(textDecoration = TextDecoration.Underline))
+ )
+ ) {
+ parseChildren(node)
+ }
+ }
+
+ is Text -> {
+ append(node.literal.clearMarkdown())
+ parseChildren(node)
+ }
+
+ else -> {
+ parseChildren(node)
+ }
+ }
+ }
+
+ private fun AnnotatedString.Builder.parseChildren(node: Node) {
+ var child = node.firstChild
+ while (child != null) {
+ parseNode(child)
+ child = child.next
+ }
+ }
+}
\ No newline at end of file
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 a9b571e4..c0850690 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
@@ -208,7 +208,7 @@ class EpubTextParser @Inject constructor(
val chapter = documentParser.run {
Jsoup.parse(content).parseDocument().dropWhile {
- it == chapterTitle // Remove chapter title if present
+ it.clearMarkdown().lowercase() == chapterTitle.lowercase()
}
}
if (chapter.isEmpty()) {
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 005d0c91..0547867d 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
@@ -7,12 +7,14 @@ import android.net.Uri
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
+import androidx.compose.ui.text.AnnotatedString
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
+import kotlinx.coroutines.yield
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.local.data_store.DataStore
import ua.acclorite.book_story.data.local.dto.FavoriteDirectoryEntity
@@ -22,13 +24,14 @@ import ua.acclorite.book_story.data.mapper.book.BookMapper
import ua.acclorite.book_story.data.mapper.color_preset.ColorPresetMapper
import ua.acclorite.book_story.data.mapper.history.HistoryMapper
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.data.remote.GithubAPI
import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.BookWithText
import ua.acclorite.book_story.domain.model.BookWithTextAndCover
-import ua.acclorite.book_story.domain.model.ChapterWithText
+import ua.acclorite.book_story.domain.model.Chapter
import ua.acclorite.book_story.domain.model.ColorPreset
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.model.NullableBook
@@ -60,6 +63,7 @@ private const val RESET_COVER = "RESET COVER, REPOSITORY"
private const val GET_ALL_SETTINGS = "GET ALL SETTINGS, REPOSITORY"
private const val GET_FILES_FROM_DEVICE = "GET FILES FROM DEVICE, REPOSITORY"
private const val CHECK_FOR_UPDATES = "CHECK FOR UPDATES, REPOSITORY"
+private const val CHECK_FOR_TEXT_UPDATE = "CHECK FOR TEXT UPDATE, REPOSITORY"
@Suppress("DEPRECATION")
@Singleton
@@ -77,6 +81,7 @@ class BookRepositoryImpl @Inject constructor(
private val fileParser: FileParser,
private val textParser: TextParser,
+ private val markdownParser: MarkdownParser
) : BookRepository {
/**
@@ -123,27 +128,115 @@ class BookRepositoryImpl @Inject constructor(
* Loads text from given path. Should be .txt.
* Used to get text from book and load Reader.
*/
- override suspend fun getBookText(textPath: String): List {
+ override suspend fun getBookText(textPath: String): List {
val textFile = File(textPath)
- val lines = mutableListOf()
+ val markdownLines = mutableListOf()
if (textPath.isBlank() || !textFile.exists() || textFile.extension != "txt") {
Log.w(GET_TEXT, "Failed to load file: $textPath")
return emptyList()
}
- withContext(Dispatchers.IO) {
- BufferedReader(FileReader(textFile)).forEachLine { line ->
- if (line.isNotBlank()) {
- lines.add(
- line.trim()
+ try {
+ withContext(Dispatchers.IO) {
+ BufferedReader(FileReader(textFile)).forEachLine { line ->
+ if (line.isNotBlank()) {
+ markdownLines.add(
+ markdownParser.parse(line.trim())
+ )
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Log.e(GET_TEXT, "Could not get text with markdown.")
+ return emptyList()
+ }
+
+ Log.i(GET_TEXT, "Successfully loaded text with markdown.")
+ return markdownLines
+ }
+
+ /**
+ * Checks whether the text of the book([bookId]) is up-to-date and did not change.
+ *
+ * @return If [Resource.Success] and returns null, then the book is up-to-date.
+ */
+ override suspend fun checkTextForUpdate(bookId: Int): Resource, List>?> {
+ yield()
+
+ try {
+ val book = database.findBookById(bookId)
+ Log.i(CHECK_FOR_TEXT_UPDATE, "Checking [${book.title}] for text update.")
+
+ yield()
+
+ val text = withContext(Dispatchers.IO) {
+ val lines = mutableListOf()
+ BufferedReader(FileReader(book.textPath)).forEachLine { line ->
+ if (line.isNotBlank()) {
+ lines.add(line.trim())
+ }
+ }
+ lines.toList()
+ }
+ val chapters = book.chapters
+ Log.i(CHECK_FOR_TEXT_UPDATE, "Got current text and chapters.")
+
+ yield()
+
+ val bookFile = File(book.filePath).apply {
+ if (!exists()) {
+ Log.e(CHECK_FOR_TEXT_UPDATE, "Couldn't get book's file: does not exist.")
+ return Resource.Error(
+ message = UIText.StringResource(
+ R.string.file_not_found,
+ name.takeLast(50)
+ )
)
}
}
- }
- Log.i(GET_TEXT, "Successfully loaded text.")
- return lines
+ yield()
+
+ val (updatedText, updatedChapters) = textParser.parse(bookFile).run {
+ if (this is Resource.Error) {
+ Log.e(CHECK_FOR_TEXT_UPDATE, "Couldn't get updated book's text.")
+ return Resource.Error(
+ message = UIText.StringResource(
+ R.string.error_file_empty
+ )
+ )
+ }
+
+ data!!.map { it.text }.flatten() to data.map { it.chapter }.run {
+ if (size < 2) return@run emptyList()
+ return@run this
+ }
+ }
+ Log.i(CHECK_FOR_TEXT_UPDATE, "Successfully got new text and chapters.")
+
+ yield()
+
+ return Resource.Success(
+ if (updatedText == text && updatedChapters == chapters) {
+ Log.i(CHECK_FOR_TEXT_UPDATE, "Text is up-to-date(${book.title}).")
+ null
+ } else {
+ Log.i(CHECK_FOR_TEXT_UPDATE, "Found difference in ${book.title}.")
+ updatedText to updatedChapters
+ }
+ )
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Log.e(CHECK_FOR_TEXT_UPDATE, "Check failed with the error: ${e.message}")
+ return Resource.Error(
+ UIText.StringResource(
+ R.string.error_query,
+ e.message ?: ""
+ )
+ )
+ }
}
/**
@@ -700,16 +793,6 @@ class BookRepositoryImpl @Inject constructor(
)
}
- /**
- * Parse text from given [file].
- * May give [Resource.Error] if something went wrong.
- *
- * @param file File to parse. Should be one of supported file formats.
- */
- override suspend fun parseText(file: File): Resource> {
- return textParser.parse(file)
- }
-
/**
* Insert history in database.
*/
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 c703106e..446bb21e 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/repository/BookRepository.kt
@@ -1,11 +1,12 @@
package ua.acclorite.book_story.domain.repository
+import androidx.compose.ui.text.AnnotatedString
import androidx.datastore.preferences.core.Preferences
import ua.acclorite.book_story.data.remote.dto.LatestReleaseInfo
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.BookWithText
import ua.acclorite.book_story.domain.model.BookWithTextAndCover
-import ua.acclorite.book_story.domain.model.ChapterWithText
+import ua.acclorite.book_story.domain.model.Chapter
import ua.acclorite.book_story.domain.model.ColorPreset
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.model.NullableBook
@@ -28,7 +29,9 @@ interface BookRepository {
suspend fun getBookText(
textPath: String
- ): List
+ ): List
+
+ suspend fun checkTextForUpdate(bookId: Int): Resource, List>?>
suspend fun insertBook(
bookWithTextAndCover: BookWithTextAndCover
@@ -79,8 +82,6 @@ interface BookRepository {
suspend fun getBookFromFile(
file: File
): NullableBook
-
- suspend fun parseText(file: File): Resource>
/* - - - - - - - - - - - - - - - - - - - - - - */
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/CheckTextForUpdate.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/CheckTextForUpdate.kt
new file mode 100644
index 00000000..ea9b35df
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/CheckTextForUpdate.kt
@@ -0,0 +1,13 @@
+package ua.acclorite.book_story.domain.use_case
+
+import ua.acclorite.book_story.domain.model.Chapter
+import ua.acclorite.book_story.domain.repository.BookRepository
+import ua.acclorite.book_story.domain.util.Resource
+import javax.inject.Inject
+
+class CheckTextForUpdate @Inject constructor(private val repository: BookRepository) {
+
+ suspend fun execute(bookId: Int): Resource, List>?> {
+ return repository.checkTextForUpdate(bookId)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt
index a3a0c933..4179ebce 100644
--- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt
+++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt
@@ -1,11 +1,12 @@
package ua.acclorite.book_story.domain.use_case
+import androidx.compose.ui.text.AnnotatedString
import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject
class GetText @Inject constructor(private val repository: BookRepository) {
- suspend fun execute(textPath: String): List {
+ suspend fun execute(textPath: String): List {
return repository.getBookText(textPath = textPath)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/ParseText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/ParseText.kt
deleted file mode 100644
index 38d9cad6..00000000
--- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/ParseText.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package ua.acclorite.book_story.domain.use_case
-
-import ua.acclorite.book_story.domain.model.ChapterWithText
-import ua.acclorite.book_story.domain.repository.BookRepository
-import ua.acclorite.book_story.domain.util.Resource
-import java.io.File
-import javax.inject.Inject
-
-class ParseText @Inject constructor(private val repository: BookRepository) {
-
- suspend fun execute(file: File): Resource> {
- return repository.parseText(file)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/core/constants/Constants.kt b/app/src/main/java/ua/acclorite/book_story/presentation/core/constants/Constants.kt
index 78f1823f..2405ac1d 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/core/constants/Constants.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/core/constants/Constants.kt
@@ -12,6 +12,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import my.nanihadesuka.compose.ScrollbarSelectionMode
import my.nanihadesuka.compose.ScrollbarSettings
@@ -457,7 +458,9 @@ private fun provideFonts() = listOf(
UIText.StringValue("Raleway"),
FontFamily(
Font(R.font.raleway_regular),
- Font(R.font.raleway_regular_italic, style = FontStyle.Italic)
+ Font(R.font.raleway_regular_italic, style = FontStyle.Italic),
+ Font(R.font.raleway_medium, weight = FontWeight.Medium),
+ Font(R.font.raleway_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic)
)
),
FontWithName(
@@ -465,7 +468,13 @@ private fun provideFonts() = listOf(
UIText.StringValue("Open Sans"),
FontFamily(
Font(R.font.opensans_regular),
- Font(R.font.opensans_regular_italic, style = FontStyle.Italic)
+ Font(R.font.opensans_regular_italic, style = FontStyle.Italic),
+ Font(R.font.opensans_medium, weight = FontWeight.Medium),
+ Font(
+ R.font.opensans_medium_italic,
+ weight = FontWeight.Medium,
+ style = FontStyle.Italic
+ )
)
),
FontWithName(
@@ -473,7 +482,9 @@ private fun provideFonts() = listOf(
UIText.StringValue("Mulish"),
FontFamily(
Font(R.font.mulish_regular),
- Font(R.font.mulish_regular_italic, style = FontStyle.Italic)
+ Font(R.font.mulish_regular_italic, style = FontStyle.Italic),
+ Font(R.font.mulish_medium, weight = FontWeight.Medium),
+ Font(R.font.mulish_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic)
)
),
FontWithName(
@@ -481,7 +492,9 @@ private fun provideFonts() = listOf(
UIText.StringValue("Arimo"),
FontFamily(
Font(R.font.arimo_regular),
- Font(R.font.arimo_regular_italic, style = FontStyle.Italic)
+ Font(R.font.arimo_regular_italic, style = FontStyle.Italic),
+ Font(R.font.arimo_medium, weight = FontWeight.Medium),
+ Font(R.font.arimo_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic)
)
),
FontWithName(
@@ -489,7 +502,13 @@ private fun provideFonts() = listOf(
UIText.StringValue("Garamond"),
FontFamily(
Font(R.font.garamond_regular),
- Font(R.font.garamond_regular_italic, style = FontStyle.Italic)
+ Font(R.font.garamond_regular_italic, style = FontStyle.Italic),
+ Font(R.font.garamond_medium, weight = FontWeight.Medium),
+ Font(
+ R.font.garamond_medium_italic,
+ weight = FontWeight.Medium,
+ style = FontStyle.Italic
+ )
)
),
FontWithName(
@@ -497,7 +516,13 @@ private fun provideFonts() = listOf(
UIText.StringValue("Roboto Serif"),
FontFamily(
Font(R.font.robotoserif_regular),
- Font(R.font.robotoserif_regular_italic, style = FontStyle.Italic)
+ Font(R.font.robotoserif_regular_italic, style = FontStyle.Italic),
+ Font(R.font.robotoserif_medium, weight = FontWeight.Medium),
+ Font(
+ R.font.robotoserif_medium_italic,
+ weight = FontWeight.Medium,
+ style = FontStyle.Italic
+ )
)
),
FontWithName(
@@ -505,7 +530,13 @@ private fun provideFonts() = listOf(
UIText.StringValue("Noto Serif"),
FontFamily(
Font(R.font.notoserif_regular),
- Font(R.font.notoserif_regular_italic, style = FontStyle.Italic)
+ Font(R.font.notoserif_regular_italic, style = FontStyle.Italic),
+ Font(R.font.notoserif_medium, weight = FontWeight.Medium),
+ Font(
+ R.font.notoserif_medium_italic,
+ weight = FontWeight.Medium,
+ style = FontStyle.Italic
+ )
)
),
FontWithName(
@@ -513,7 +544,13 @@ private fun provideFonts() = listOf(
UIText.StringValue("Noto Sans"),
FontFamily(
Font(R.font.notosans_regular),
- Font(R.font.notosans_regular_italic, style = FontStyle.Italic)
+ Font(R.font.notosans_regular_italic, style = FontStyle.Italic),
+ Font(R.font.notosans_medium, weight = FontWeight.Medium),
+ Font(
+ R.font.notosans_medium_italic,
+ weight = FontWeight.Medium,
+ style = FontStyle.Italic
+ )
)
),
FontWithName(
@@ -521,7 +558,9 @@ private fun provideFonts() = listOf(
UIText.StringValue("Roboto"),
FontFamily(
Font(R.font.roboto_regular),
- Font(R.font.roboto_regular_italic, style = FontStyle.Italic)
+ Font(R.font.roboto_regular_italic, style = FontStyle.Italic),
+ Font(R.font.roboto_medium, weight = FontWeight.Medium),
+ Font(R.font.roboto_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic)
)
),
FontWithName(
@@ -529,7 +568,9 @@ private fun provideFonts() = listOf(
UIText.StringValue("Jost"),
FontFamily(
Font(R.font.jost_regular),
- Font(R.font.jost_regular_italic, style = FontStyle.Italic)
+ Font(R.font.jost_regular_italic, style = FontStyle.Italic),
+ Font(R.font.jost_medium, weight = FontWeight.Medium),
+ Font(R.font.jost_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic)
)
),
FontWithName(
@@ -537,7 +578,13 @@ private fun provideFonts() = listOf(
UIText.StringValue("Merriweather"),
FontFamily(
Font(R.font.merriweather_regular),
- Font(R.font.merriweather_regular_italic, style = FontStyle.Italic)
+ Font(R.font.merriweather_regular_italic, style = FontStyle.Italic),
+ Font(R.font.merriweather_medium, weight = FontWeight.Medium),
+ Font(
+ R.font.merriweather_medium_italic,
+ weight = FontWeight.Medium,
+ style = FontStyle.Italic
+ )
)
),
FontWithName(
@@ -545,7 +592,13 @@ private fun provideFonts() = listOf(
UIText.StringValue("Montserrat"),
FontFamily(
Font(R.font.montserrat_regular),
- Font(R.font.montserrat_regular_italic, style = FontStyle.Italic)
+ Font(R.font.montserrat_regular_italic, style = FontStyle.Italic),
+ Font(R.font.montserrat_medium, weight = FontWeight.Medium),
+ Font(
+ R.font.montserrat_medium_italic,
+ weight = FontWeight.Medium,
+ style = FontStyle.Italic
+ )
)
),
FontWithName(
@@ -553,14 +606,17 @@ private fun provideFonts() = listOf(
UIText.StringValue("Nunito"),
FontFamily(
Font(R.font.nunito_regular),
- Font(R.font.nunito_regular_italic, style = FontStyle.Italic)
+ Font(R.font.nunito_regular_italic, style = FontStyle.Italic),
+ Font(R.font.nunito_medium, weight = FontWeight.Medium),
+ Font(R.font.nunito_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic)
)
),
FontWithName(
"roboto_slab",
UIText.StringValue("Roboto Slab"),
FontFamily(
- Font(R.font.robotoslab_regular)
+ Font(R.font.robotoslab_regular),
+ Font(R.font.robotoslab_medium, weight = FontWeight.Medium),
)
),
FontWithName(
@@ -568,7 +624,9 @@ private fun provideFonts() = listOf(
UIText.StringValue("Lora"),
FontFamily(
Font(R.font.lora_regular),
- Font(R.font.lora_regular_italic, style = FontStyle.Italic)
+ Font(R.font.lora_regular_italic, style = FontStyle.Italic),
+ Font(R.font.lora_medium, weight = FontWeight.Medium),
+ Font(R.font.lora_medium_italic, weight = FontWeight.Medium, style = FontStyle.Italic)
)
)
)
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/core/util/Extensions.kt b/app/src/main/java/ua/acclorite/book_story/presentation/core/util/Extensions.kt
index 89cb6e5f..e08270ba 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/core/util/Extensions.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/core/util/Extensions.kt
@@ -31,6 +31,7 @@ fun Intent.launchActivity(
try {
activity.baseContext.startActivity(intent)
} catch (e: Exception) {
+ e.printStackTrace()
error()
return
}
@@ -64,5 +65,5 @@ fun Modifier.noRippleClickable(
}
fun String.clearMarkdown(): String {
- return replace(Regex("_|\\*\\*"), "").trim()
+ return replace(Regex("_|\\*\\*"), "")
}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt
index ef07ee1b..ad535e82 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt
@@ -26,11 +26,10 @@ import ua.acclorite.book_story.domain.model.BookWithText
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.use_case.CanResetCover
+import ua.acclorite.book_story.domain.use_case.CheckTextForUpdate
import ua.acclorite.book_story.domain.use_case.DeleteBooks
import ua.acclorite.book_story.domain.use_case.GetBookById
-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.ParseText
import ua.acclorite.book_story.domain.use_case.ResetCoverImage
import ua.acclorite.book_story.domain.use_case.UpdateBook
import ua.acclorite.book_story.domain.use_case.UpdateBookWithText
@@ -40,7 +39,6 @@ import ua.acclorite.book_story.domain.util.Resource
import ua.acclorite.book_story.domain.util.UIText
import ua.acclorite.book_story.presentation.core.navigation.Screen
import ua.acclorite.book_story.presentation.core.util.BaseViewModel
-import java.io.File
import java.util.Date
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -54,10 +52,9 @@ class BookInfoViewModel @Inject constructor(
private val insertHistory: InsertHistory,
private val deleteBooks: DeleteBooks,
private val getBookById: GetBookById,
- private val getText: GetText,
private val canResetCover: CanResetCover,
private val resetCoverImage: ResetCoverImage,
- private val parseText: ParseText
+ private val checkTextForUpdate: CheckTextForUpdate,
) : BaseViewModel() {
private val _state = MutableStateFlow(BookInfoState())
@@ -468,14 +465,46 @@ class BookInfoViewModel @Inject constructor(
yield()
- val currentBook = _state.value.book.apply {
- if (!File(filePath).exists()) {
+ val result = checkTextForUpdate.execute(bookId = _state.value.book.id)
+ when (result) {
+ is Resource.Success -> {
+ if (result.data == null) {
+ onEvent(
+ BookInfoEvent.OnShowSnackbar(
+ event.context.getString(R.string.nothing_changed),
+ action = null,
+ durationMillis = 4000L,
+ snackbarState = event.snackbarState
+ )
+ )
+ delay(500)
+ _state.update {
+ it.copy(
+ checkingForUpdate = false
+ )
+ }
+ return@launch
+ } else {
+ onEvent(
+ BookInfoEvent.OnShowConfirmUpdateDialog(
+ updatedText = result.data.first,
+ updatedChapters = result.data.second
+ )
+ )
+
+ _state.update {
+ it.copy(
+ checkingForUpdate = false
+ )
+ }
+ return@launch
+ }
+ }
+
+ is Resource.Error -> {
onEvent(
BookInfoEvent.OnShowSnackbar(
- text = event.context.getString(
- R.string.file_not_found,
- filePath.substringAfterLast("/").takeLast(25)
- ),
+ text = result.message?.asString(event.context) ?: "",
action = event.context.getString(R.string.retry),
onAction = {
onEvent(
@@ -499,83 +528,6 @@ class BookInfoViewModel @Inject constructor(
return@launch
}
}
- val updatedBook = parseText.execute(File(currentBook.filePath)).apply {
- if (this is Resource.Error || data == null) {
- onEvent(
- BookInfoEvent.OnShowSnackbar(
- text = message?.asString(event.context)
- ?: event.context.getString(
- R.string.error_something_went_wrong_with_file
- ),
- action = event.context.getString(R.string.retry),
- onAction = {
- onEvent(
- BookInfoEvent.OnCheckForUpdate(
- snackbarState = event.snackbarState,
- context = event.context
- )
- )
- },
- durationMillis = 4000L,
- snackbarState = event.snackbarState
- )
- )
-
- delay(500)
- _state.update {
- it.copy(
- checkingForUpdate = false
- )
- }
- return@launch
- }
- }.data!!
-
- yield()
-
- val updatedText = updatedBook.map { it.text }.flatten()
- val text = getText.execute(currentBook.textPath)
-
- val updatedChapters = updatedBook.map { it.chapter }.run {
- if (size < 2) return@run emptyList()
- return@run this
- }
- val chapters = currentBook.chapters
-
- yield()
-
- if (updatedText == text && updatedChapters == chapters) {
- onEvent(
- BookInfoEvent.OnShowSnackbar(
- event.context.getString(R.string.nothing_changed),
- action = null,
- durationMillis = 4000L,
- snackbarState = event.snackbarState
- )
- )
- delay(500)
- _state.update {
- it.copy(
- checkingForUpdate = false
- )
- }
- return@launch
- }
-
- yield()
-
- onEvent(
- BookInfoEvent.OnShowConfirmUpdateDialog(
- updatedText = updatedText,
- updatedChapters = updatedChapters
- )
- )
-
- _state.update {
- it.copy(
- checkingForUpdate = false
- )
- }
}
}
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt
index d09bc6b2..6f74d669 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt
@@ -494,7 +494,6 @@ private fun ReaderScreen(lazyListState: LazyListState) {
ReaderTextParagraph(
line = line,
- context = context,
fontFamily = fontFamily,
fontColor = fontColor.value,
lineHeight = lineHeight,
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt
index 7f8aa09d..285da091 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt
@@ -1,17 +1,21 @@
package ua.acclorite.book_story.presentation.screens.reader.components
-import android.content.Context
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.platform.LocalContext
+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
@@ -19,6 +23,7 @@ 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.R
import ua.acclorite.book_story.domain.model.FontWithName
import ua.acclorite.book_story.presentation.core.components.LocalReaderViewModel
@@ -31,7 +36,6 @@ import ua.acclorite.book_story.presentation.screens.settings.nested.reader.data.
* Reader Text Paragraph item.
*
* @param line Current line.
- * @param context Context.
* @param fontFamily Line's font family.
* @param fontColor Line's font color.
* @param lineHeight Line's line height.
@@ -47,8 +51,7 @@ import ua.acclorite.book_story.presentation.screens.settings.nested.reader.data.
*/
@Composable
fun LazyItemScope.ReaderTextParagraph(
- line: String,
- context: Context,
+ line: AnnotatedString,
fontFamily: FontWithName,
fontColor: Color,
lineHeight: TextUnit,
@@ -63,14 +66,13 @@ fun LazyItemScope.ReaderTextParagraph(
toolbarHidden: Boolean
) {
val onEvent = LocalReaderViewModel.current.onEvent
+ val context = LocalContext.current
Column(
modifier = Modifier
.animateItem(fadeInSpec = null, fadeOutSpec = null)
.fillMaxWidth()
- .padding(
- horizontal = sidePadding
- ),
+ .padding(horizontal = sidePadding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = when (textAlignment) {
ReaderTextAlignment.START, ReaderTextAlignment.JUSTIFY -> Alignment.Start
@@ -78,57 +80,69 @@ fun LazyItemScope.ReaderTextParagraph(
ReaderTextAlignment.END -> Alignment.End
}
) {
- BasicText(
- text = line,
- modifier = Modifier.then(
- if (
- doubleClickTranslationEnabled &&
- toolbarHidden
- ) {
- Modifier.noRippleClickable(
- onDoubleClick = {
- onEvent(
- ReaderEvent.OnOpenTranslator(
- textToTranslate = line,
- translateWholeParagraph = true,
- context = context as ComponentActivity,
- noAppsFound = {
- context.getString(R.string.error_no_translator)
- .showToast(context = context, longToast = false)
- }
- )
- )
- },
- onClick = {
- onEvent(
- ReaderEvent.OnShowHideMenu(
- fullscreenMode = fullscreenMode,
- saveCheckpoint = true,
- activity = context as ComponentActivity
- )
+ when (line.text) {
+ "---" -> {
+ HorizontalDivider(
+ thickness = 3.dp,
+ modifier = Modifier.clip(CircleShape),
+ color = fontColor.copy(0.3f)
+ )
+ }
+
+ else -> {
+ BasicText(
+ text = line,
+ modifier = Modifier.then(
+ if (
+ doubleClickTranslationEnabled &&
+ toolbarHidden
+ ) {
+ Modifier.noRippleClickable(
+ onDoubleClick = {
+ onEvent(
+ ReaderEvent.OnOpenTranslator(
+ textToTranslate = line.text,
+ translateWholeParagraph = true,
+ context = context as ComponentActivity,
+ noAppsFound = {
+ context.getString(R.string.error_no_translator)
+ .showToast(context = context, longToast = false)
+ }
+ )
+ )
+ },
+ onClick = {
+ onEvent(
+ ReaderEvent.OnShowHideMenu(
+ fullscreenMode = fullscreenMode,
+ saveCheckpoint = true,
+ activity = context as ComponentActivity
+ )
+ )
+ }
)
+ } 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
)
- } 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/screens/reader/data/ReaderState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt
index 66b4d670..5d298d64 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt
@@ -2,6 +2,7 @@ package ua.acclorite.book_story.presentation.screens.reader.data
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.AnnotatedString
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Chapter
import ua.acclorite.book_story.domain.util.UIText
@@ -10,7 +11,7 @@ import ua.acclorite.book_story.presentation.core.constants.Constants
@Immutable
data class ReaderState(
val book: Book = Constants.EMPTY_BOOK,
- val text: List = emptyList(),
+ val text: List = emptyList(),
val listState: LazyListState = LazyListState(),
val currentChapter: Chapter? = null,
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt
index 9c7a2ee3..ae76a72e 100644
--- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt
@@ -26,10 +26,10 @@ import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.Book
+import ua.acclorite.book_story.domain.use_case.CheckTextForUpdate
import ua.acclorite.book_story.domain.use_case.GetBookById
import ua.acclorite.book_story.domain.use_case.GetLatestHistory
import ua.acclorite.book_story.domain.use_case.GetText
-import ua.acclorite.book_story.domain.use_case.ParseText
import ua.acclorite.book_story.domain.use_case.UpdateBook
import ua.acclorite.book_story.domain.util.OnNavigate
import ua.acclorite.book_story.domain.util.Resource
@@ -37,7 +37,6 @@ import ua.acclorite.book_story.domain.util.UIText
import ua.acclorite.book_story.presentation.core.navigation.Screen
import ua.acclorite.book_story.presentation.core.util.BaseViewModel
import ua.acclorite.book_story.presentation.core.util.launchActivity
-import java.io.File
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -48,7 +47,7 @@ class ReaderViewModel @Inject constructor(
private val getText: GetText,
private val getLatestHistory: GetLatestHistory,
private val getBookById: GetBookById,
- private val parseText: ParseText
+ private val checkTextForUpdate: CheckTextForUpdate
) : BaseViewModel() {
private val _state = MutableStateFlow(ReaderState())
@@ -421,8 +420,33 @@ class ReaderViewModel @Inject constructor(
yield()
- val currentBook = _state.value.book.apply {
- if (!File(filePath).exists()) {
+ val result = checkTextForUpdate.execute(bookId = _state.value.book.id)
+ when (result) {
+ is Resource.Success -> {
+ if (result.data == null) {
+ withContext(Dispatchers.Main) {
+ event.noUpdateFound()
+ }
+ _state.update {
+ it.copy(
+ checkingForUpdate = false,
+ updateFound = false
+ )
+ }
+ return@launch
+ } else {
+ _state.update {
+ it.copy(
+ updateFound = true,
+ checkingForUpdate = false
+ )
+ }
+ onEvent(ReaderEvent.OnShowHideUpdateDialog(true))
+ return@launch
+ }
+ }
+
+ is Resource.Error -> {
_state.update {
it.copy(
checkingForUpdate = false,
@@ -432,51 +456,6 @@ class ReaderViewModel @Inject constructor(
return@launch
}
}
- val updatedBook = parseText.execute(File(currentBook.filePath)).apply {
- if (this is Resource.Error || data == null) {
- _state.update {
- it.copy(
- checkingForUpdate = false,
- updateFound = false
- )
- }
- return@launch
- }
- }.data!!
-
- yield()
-
- val updatedText = updatedBook.map { it.text }.flatten()
- val text = getText.execute(currentBook.textPath)
-
- val updatedChapters = updatedBook.map { it.chapter }.run {
- if (size < 2) return@run emptyList()
- return@run this
- }
- val chapters = currentBook.chapters
-
- yield()
-
- if (updatedText == text && updatedChapters == chapters) {
- withContext(Dispatchers.Main) {
- event.noUpdateFound()
- }
- _state.update {
- it.copy(
- checkingForUpdate = false,
- updateFound = false
- )
- }
- return@launch
- }
-
- _state.update {
- it.copy(
- updateFound = true,
- checkingForUpdate = false
- )
- }
- onEvent(ReaderEvent.OnShowHideUpdateDialog(true))
}
}
diff --git a/app/src/main/res/font/arimo_medium.ttf b/app/src/main/res/font/arimo_medium.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..f2223e205687315a998cd3b10013a0ade4ff3a7b
GIT binary patch
literal 320664
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
2VlBx+(h
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