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 00000000..f2223e20 Binary files /dev/null and b/app/src/main/res/font/arimo_medium.ttf differ diff --git a/app/src/main/res/font/arimo_medium_italic.ttf b/app/src/main/res/font/arimo_medium_italic.ttf new file mode 100644 index 00000000..7572c83a Binary files /dev/null and b/app/src/main/res/font/arimo_medium_italic.ttf differ diff --git a/app/src/main/res/font/garamond_medium.ttf b/app/src/main/res/font/garamond_medium.ttf new file mode 100644 index 00000000..eb127af9 Binary files /dev/null and b/app/src/main/res/font/garamond_medium.ttf differ diff --git a/app/src/main/res/font/garamond_medium_italic.ttf b/app/src/main/res/font/garamond_medium_italic.ttf new file mode 100644 index 00000000..c8eae1f4 Binary files /dev/null and b/app/src/main/res/font/garamond_medium_italic.ttf differ diff --git a/app/src/main/res/font/garamond_regular.ttf b/app/src/main/res/font/garamond_regular.ttf index d3d6f3fb..f8c490b1 100644 Binary files a/app/src/main/res/font/garamond_regular.ttf and b/app/src/main/res/font/garamond_regular.ttf differ diff --git a/app/src/main/res/font/garamond_regular_italic.ttf b/app/src/main/res/font/garamond_regular_italic.ttf index 0f76a8ec..5c44af14 100644 Binary files a/app/src/main/res/font/garamond_regular_italic.ttf and b/app/src/main/res/font/garamond_regular_italic.ttf differ diff --git a/app/src/main/res/font/jost_medium.ttf b/app/src/main/res/font/jost_medium.ttf new file mode 100644 index 00000000..2f74a13d Binary files /dev/null and b/app/src/main/res/font/jost_medium.ttf differ diff --git a/app/src/main/res/font/jost_medium_italic.ttf b/app/src/main/res/font/jost_medium_italic.ttf new file mode 100644 index 00000000..42bdd20d Binary files /dev/null and b/app/src/main/res/font/jost_medium_italic.ttf differ diff --git a/app/src/main/res/font/lora_medium.ttf b/app/src/main/res/font/lora_medium.ttf new file mode 100644 index 00000000..85ca5a27 Binary files /dev/null and b/app/src/main/res/font/lora_medium.ttf differ diff --git a/app/src/main/res/font/lora_medium_italic.ttf b/app/src/main/res/font/lora_medium_italic.ttf new file mode 100644 index 00000000..42208fbe Binary files /dev/null and b/app/src/main/res/font/lora_medium_italic.ttf differ diff --git a/app/src/main/res/font/merriweather_medium.ttf b/app/src/main/res/font/merriweather_medium.ttf new file mode 100644 index 00000000..3e10e02f Binary files /dev/null and b/app/src/main/res/font/merriweather_medium.ttf differ diff --git a/app/src/main/res/font/merriweather_medium_italic.ttf b/app/src/main/res/font/merriweather_medium_italic.ttf new file mode 100644 index 00000000..5b9d0ec5 Binary files /dev/null and b/app/src/main/res/font/merriweather_medium_italic.ttf differ diff --git a/app/src/main/res/font/montserrat_medium.ttf b/app/src/main/res/font/montserrat_medium.ttf new file mode 100644 index 00000000..4012225c Binary files /dev/null and b/app/src/main/res/font/montserrat_medium.ttf differ diff --git a/app/src/main/res/font/montserrat_medium_italic.ttf b/app/src/main/res/font/montserrat_medium_italic.ttf new file mode 100644 index 00000000..84b25394 Binary files /dev/null and b/app/src/main/res/font/montserrat_medium_italic.ttf differ diff --git a/app/src/main/res/font/mulish_medium.ttf b/app/src/main/res/font/mulish_medium.ttf new file mode 100644 index 00000000..be50c597 Binary files /dev/null and b/app/src/main/res/font/mulish_medium.ttf differ diff --git a/app/src/main/res/font/mulish_medium_italic.ttf b/app/src/main/res/font/mulish_medium_italic.ttf new file mode 100644 index 00000000..01cf746f Binary files /dev/null and b/app/src/main/res/font/mulish_medium_italic.ttf differ diff --git a/app/src/main/res/font/notosans_medium.ttf b/app/src/main/res/font/notosans_medium.ttf new file mode 100644 index 00000000..a799b74d Binary files /dev/null and b/app/src/main/res/font/notosans_medium.ttf differ diff --git a/app/src/main/res/font/notosans_medium_italic.ttf b/app/src/main/res/font/notosans_medium_italic.ttf new file mode 100644 index 00000000..2ccbc5b0 Binary files /dev/null and b/app/src/main/res/font/notosans_medium_italic.ttf differ diff --git a/app/src/main/res/font/notoserif_medium.ttf b/app/src/main/res/font/notoserif_medium.ttf new file mode 100644 index 00000000..7a177074 Binary files /dev/null and b/app/src/main/res/font/notoserif_medium.ttf differ diff --git a/app/src/main/res/font/notoserif_medium_italic.ttf b/app/src/main/res/font/notoserif_medium_italic.ttf new file mode 100644 index 00000000..3aa6bf49 Binary files /dev/null and b/app/src/main/res/font/notoserif_medium_italic.ttf differ diff --git a/app/src/main/res/font/nunito_medium.ttf b/app/src/main/res/font/nunito_medium.ttf new file mode 100644 index 00000000..e24c1d61 Binary files /dev/null and b/app/src/main/res/font/nunito_medium.ttf differ diff --git a/app/src/main/res/font/nunito_medium_italic.ttf b/app/src/main/res/font/nunito_medium_italic.ttf new file mode 100644 index 00000000..6fec0957 Binary files /dev/null and b/app/src/main/res/font/nunito_medium_italic.ttf differ diff --git a/app/src/main/res/font/opensans_medium.ttf b/app/src/main/res/font/opensans_medium.ttf new file mode 100644 index 00000000..ae716936 Binary files /dev/null and b/app/src/main/res/font/opensans_medium.ttf differ diff --git a/app/src/main/res/font/opensans_medium_italic.ttf b/app/src/main/res/font/opensans_medium_italic.ttf new file mode 100644 index 00000000..6d1e09b2 Binary files /dev/null and b/app/src/main/res/font/opensans_medium_italic.ttf differ diff --git a/app/src/main/res/font/raleway_medium.ttf b/app/src/main/res/font/raleway_medium.ttf new file mode 100644 index 00000000..c6fa5335 Binary files /dev/null and b/app/src/main/res/font/raleway_medium.ttf differ diff --git a/app/src/main/res/font/raleway_medium_italic.ttf b/app/src/main/res/font/raleway_medium_italic.ttf new file mode 100644 index 00000000..c34ff6a5 Binary files /dev/null and b/app/src/main/res/font/raleway_medium_italic.ttf differ diff --git a/app/src/main/res/font/roboto_medium.ttf b/app/src/main/res/font/roboto_medium.ttf new file mode 100644 index 00000000..0707e15c Binary files /dev/null and b/app/src/main/res/font/roboto_medium.ttf differ diff --git a/app/src/main/res/font/roboto_medium_italic.ttf b/app/src/main/res/font/roboto_medium_italic.ttf new file mode 100644 index 00000000..4e3bf0d2 Binary files /dev/null and b/app/src/main/res/font/roboto_medium_italic.ttf differ diff --git a/app/src/main/res/font/robotoserif_medium.ttf b/app/src/main/res/font/robotoserif_medium.ttf new file mode 100644 index 00000000..119187c4 Binary files /dev/null and b/app/src/main/res/font/robotoserif_medium.ttf differ diff --git a/app/src/main/res/font/robotoserif_medium_italic.ttf b/app/src/main/res/font/robotoserif_medium_italic.ttf new file mode 100644 index 00000000..96066b45 Binary files /dev/null and b/app/src/main/res/font/robotoserif_medium_italic.ttf differ diff --git a/app/src/main/res/font/robotoslab_medium.ttf b/app/src/main/res/font/robotoslab_medium.ttf new file mode 100644 index 00000000..44377207 Binary files /dev/null and b/app/src/main/res/font/robotoslab_medium.ttf differ