🚀 Markdown support in Reader
* 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
This commit is contained in:
parent
2e041f0cbb
commit
b8721b6ad7
48 changed files with 503 additions and 276 deletions
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -2,41 +2,56 @@ 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 <p>.
|
||||
* 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<String> {
|
||||
val lines = mutableListOf<String>()
|
||||
|
||||
yield()
|
||||
|
||||
body()
|
||||
.select("p")
|
||||
.apply {
|
||||
forEach { element ->
|
||||
body().apply {
|
||||
// Remove manual line breaks from all <p>
|
||||
select("p").forEach { element ->
|
||||
yield()
|
||||
|
||||
val cleanedText = element.html().replace(Regex("\\n+"), " ")
|
||||
element.html(cleanedText)
|
||||
element.html(element.html().replace(Regex("\\n+"), " "))
|
||||
element.append("\n")
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
body()
|
||||
.wholeText()
|
||||
.lines()
|
||||
.forEach { line ->
|
||||
yield()
|
||||
if (line.isNotBlank()) {
|
||||
lines.add(line.trim())
|
||||
val formattedLine = line.replace(
|
||||
Regex("""\*\*\s*(.*?)\s*\*\*"""), "**$1**"
|
||||
).replace(
|
||||
Regex("""_\s*(.*?)\s*_"""), "_$1_"
|
||||
).trim()
|
||||
|
||||
if (formattedLine.clearMarkdown().isNotBlank()) {
|
||||
lines.add(formattedLine)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
override suspend fun getBookText(textPath: String): List<AnnotatedString> {
|
||||
val textFile = File(textPath)
|
||||
val lines = mutableListOf<String>()
|
||||
val markdownLines = mutableListOf<AnnotatedString>()
|
||||
|
||||
if (textPath.isBlank() || !textFile.exists() || textFile.extension != "txt") {
|
||||
Log.w(GET_TEXT, "Failed to load file: $textPath")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
BufferedReader(FileReader(textFile)).forEachLine { line ->
|
||||
if (line.isNotBlank()) {
|
||||
lines.add(
|
||||
line.trim()
|
||||
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.")
|
||||
return lines
|
||||
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<Pair<List<String>, List<Chapter>>?> {
|
||||
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<String>()
|
||||
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)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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<List<ChapterWithText>> {
|
||||
return textParser.parse(file)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert history in database.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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<String>
|
||||
): List<AnnotatedString>
|
||||
|
||||
suspend fun checkTextForUpdate(bookId: Int): Resource<Pair<List<String>, List<Chapter>>?>
|
||||
|
||||
suspend fun insertBook(
|
||||
bookWithTextAndCover: BookWithTextAndCover
|
||||
|
|
@ -79,8 +82,6 @@ interface BookRepository {
|
|||
suspend fun getBookFromFile(
|
||||
file: File
|
||||
): NullableBook
|
||||
|
||||
suspend fun parseText(file: File): Resource<List<ChapterWithText>>
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Pair<List<String>, List<Chapter>>?> {
|
||||
return repository.checkTextForUpdate(bookId)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> {
|
||||
suspend fun execute(textPath: String): List<AnnotatedString> {
|
||||
return repository.getBookText(textPath = textPath)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<List<ChapterWithText>> {
|
||||
return repository.parseText(file)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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("_|\\*\\*"), "")
|
||||
}
|
||||
|
|
@ -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<BookInfoState, BookInfoEvent>() {
|
||||
|
||||
private val _state = MutableStateFlow(BookInfoState())
|
||||
|
|
@ -468,83 +465,10 @@ class BookInfoViewModel @Inject constructor(
|
|||
|
||||
yield()
|
||||
|
||||
val currentBook = _state.value.book.apply {
|
||||
if (!File(filePath).exists()) {
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowSnackbar(
|
||||
text = event.context.getString(
|
||||
R.string.file_not_found,
|
||||
filePath.substringAfterLast("/").takeLast(25)
|
||||
),
|
||||
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
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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),
|
||||
|
|
@ -560,14 +484,11 @@ class BookInfoViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
yield()
|
||||
|
||||
} else {
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowConfirmUpdateDialog(
|
||||
updatedText = updatedText,
|
||||
updatedChapters = updatedChapters
|
||||
updatedText = result.data.first,
|
||||
updatedChapters = result.data.second
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -576,6 +497,37 @@ class BookInfoViewModel @Inject constructor(
|
|||
checkingForUpdate = false
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
onEvent(
|
||||
BookInfoEvent.OnShowSnackbar(
|
||||
text = result.message?.asString(event.context) ?: "",
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -494,7 +494,6 @@ private fun ReaderScreen(lazyListState: LazyListState) {
|
|||
|
||||
ReaderTextParagraph(
|
||||
line = line,
|
||||
context = context,
|
||||
fontFamily = fontFamily,
|
||||
fontColor = fontColor.value,
|
||||
lineHeight = lineHeight,
|
||||
|
|
|
|||
|
|
@ -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,6 +80,16 @@ fun LazyItemScope.ReaderTextParagraph(
|
|||
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(
|
||||
|
|
@ -89,7 +101,7 @@ fun LazyItemScope.ReaderTextParagraph(
|
|||
onDoubleClick = {
|
||||
onEvent(
|
||||
ReaderEvent.OnOpenTranslator(
|
||||
textToTranslate = line,
|
||||
textToTranslate = line.text,
|
||||
translateWholeParagraph = true,
|
||||
context = context as ComponentActivity,
|
||||
noAppsFound = {
|
||||
|
|
@ -131,4 +143,6 @@ fun LazyItemScope.ReaderTextParagraph(
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> = emptyList(),
|
||||
val text: List<AnnotatedString> = emptyList(),
|
||||
val listState: LazyListState = LazyListState(),
|
||||
|
||||
val currentChapter: Chapter? = null,
|
||||
|
|
|
|||
|
|
@ -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<ReaderState, ReaderEvent>() {
|
||||
|
||||
private val _state = MutableStateFlow(ReaderState())
|
||||
|
|
@ -421,43 +420,10 @@ class ReaderViewModel @Inject constructor(
|
|||
|
||||
yield()
|
||||
|
||||
val currentBook = _state.value.book.apply {
|
||||
if (!File(filePath).exists()) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
checkingForUpdate = false,
|
||||
updateFound = false
|
||||
)
|
||||
}
|
||||
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) {
|
||||
val result = checkTextForUpdate.execute(bookId = _state.value.book.id)
|
||||
when (result) {
|
||||
is Resource.Success -> {
|
||||
if (result.data == null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
event.noUpdateFound()
|
||||
}
|
||||
|
|
@ -468,8 +434,7 @@ class ReaderViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
} else {
|
||||
_state.update {
|
||||
it.copy(
|
||||
updateFound = true,
|
||||
|
|
@ -477,6 +442,20 @@ class ReaderViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
onEvent(ReaderEvent.OnShowHideUpdateDialog(true))
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
is Resource.Error -> {
|
||||
_state.update {
|
||||
it.copy(
|
||||
checkingForUpdate = false,
|
||||
updateFound = false
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
app/src/main/res/font/arimo_medium.ttf
Normal file
BIN
app/src/main/res/font/arimo_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/arimo_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/arimo_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/garamond_medium.ttf
Normal file
BIN
app/src/main/res/font/garamond_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/garamond_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/garamond_medium_italic.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/src/main/res/font/jost_medium.ttf
Normal file
BIN
app/src/main/res/font/jost_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/jost_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/jost_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/lora_medium.ttf
Normal file
BIN
app/src/main/res/font/lora_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/lora_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/lora_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/merriweather_medium.ttf
Normal file
BIN
app/src/main/res/font/merriweather_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/merriweather_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/merriweather_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/montserrat_medium.ttf
Normal file
BIN
app/src/main/res/font/montserrat_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/montserrat_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/montserrat_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/mulish_medium.ttf
Normal file
BIN
app/src/main/res/font/mulish_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/mulish_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/mulish_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/notosans_medium.ttf
Normal file
BIN
app/src/main/res/font/notosans_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/notosans_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/notosans_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/notoserif_medium.ttf
Normal file
BIN
app/src/main/res/font/notoserif_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/notoserif_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/notoserif_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/nunito_medium.ttf
Normal file
BIN
app/src/main/res/font/nunito_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/nunito_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/nunito_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/opensans_medium.ttf
Normal file
BIN
app/src/main/res/font/opensans_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/opensans_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/opensans_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/raleway_medium.ttf
Normal file
BIN
app/src/main/res/font/raleway_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/raleway_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/raleway_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/roboto_medium.ttf
Normal file
BIN
app/src/main/res/font/roboto_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/roboto_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/roboto_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/robotoserif_medium.ttf
Normal file
BIN
app/src/main/res/font/robotoserif_medium.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/robotoserif_medium_italic.ttf
Normal file
BIN
app/src/main/res/font/robotoserif_medium_italic.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/robotoslab_medium.ttf
Normal file
BIN
app/src/main/res/font/robotoslab_medium.ttf
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue