🚀 Add and Improve Parsers

Added new supported file extensions: ".zip", ".html" and ".htm". Improved parsers: ".epub".

Partly Resolves(no ".cbz"): #48
This commit is contained in:
Acclorite 2024-08-23 12:51:01 +03:00
parent 5e2cc28c92
commit e1fdac0f39
13 changed files with 987 additions and 536 deletions

View file

@ -2,8 +2,11 @@ package ua.acclorite.book_story.data.parser
import ua.acclorite.book_story.data.parser.epub.EpubFileParser import ua.acclorite.book_story.data.parser.epub.EpubFileParser
import ua.acclorite.book_story.data.parser.fb2.Fb2FileParser import ua.acclorite.book_story.data.parser.fb2.Fb2FileParser
import ua.acclorite.book_story.data.parser.htm.HtmFileParser
import ua.acclorite.book_story.data.parser.html.HtmlFileParser
import ua.acclorite.book_story.data.parser.pdf.PdfFileParser import ua.acclorite.book_story.data.parser.pdf.PdfFileParser
import ua.acclorite.book_story.data.parser.txt.TxtFileParser import ua.acclorite.book_story.data.parser.txt.TxtFileParser
import ua.acclorite.book_story.data.parser.zip.ZipFileParser
import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.util.CoverImage import ua.acclorite.book_story.domain.util.CoverImage
import java.io.File import java.io.File
@ -13,32 +16,48 @@ class FileParserImpl @Inject constructor(
private val txtFileParser: TxtFileParser, private val txtFileParser: TxtFileParser,
private val pdfFileParser: PdfFileParser, private val pdfFileParser: PdfFileParser,
private val epubFileParser: EpubFileParser, private val epubFileParser: EpubFileParser,
private val fb2FileParser: Fb2FileParser private val fb2FileParser: Fb2FileParser,
private val zipFileParser: ZipFileParser,
private val htmlFileParser: HtmlFileParser,
private val htmFileParser: HtmFileParser,
) : FileParser { ) : FileParser {
override suspend fun parse(file: File): Pair<Book, CoverImage?>? { override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
if (!file.exists()) { if (!file.exists()) {
return null return null
} }
val fileFormat = ".${file.name.substringAfterLast(".")}".lowercase().trim() val fileFormat = ".${file.extension}".lowercase().trim()
return when (fileFormat) {
".pdf" -> {
pdfFileParser.parse(file)
}
if (fileFormat == ".pdf") { ".epub" -> {
return pdfFileParser.parse(file) epubFileParser.parse(file)
}
".txt" -> {
txtFileParser.parse(file)
}
".fb2" -> {
fb2FileParser.parse(file)
}
".zip" -> {
zipFileParser.parse(file)
}
".html" -> {
htmlFileParser.parse(file)
}
".htm" -> {
htmFileParser.parse(file)
}
else -> null
} }
if (fileFormat == ".epub") {
return epubFileParser.parse(file)
}
if (fileFormat == ".txt") {
return txtFileParser.parse(file)
}
if (fileFormat == ".fb2") {
return fb2FileParser.parse(file)
}
return null
} }
} }

View file

@ -1,10 +1,12 @@
package ua.acclorite.book_story.data.parser package ua.acclorite.book_story.data.parser
import ua.acclorite.book_story.R import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.epub.EpubTextParser
import ua.acclorite.book_story.data.parser.fb2.Fb2TextParser import ua.acclorite.book_story.data.parser.fb2.Fb2TextParser
import ua.acclorite.book_story.data.parser.htm.HtmTextParser
import ua.acclorite.book_story.data.parser.html.HtmlTextParser
import ua.acclorite.book_story.data.parser.pdf.PdfTextParser import ua.acclorite.book_story.data.parser.pdf.PdfTextParser
import ua.acclorite.book_story.data.parser.txt.TxtTextParser import ua.acclorite.book_story.data.parser.txt.TxtTextParser
import ua.acclorite.book_story.data.parser.zip.ZipTextParser
import ua.acclorite.book_story.domain.util.Resource import ua.acclorite.book_story.domain.util.Resource
import ua.acclorite.book_story.domain.util.UIText import ua.acclorite.book_story.domain.util.UIText
import java.io.File import java.io.File
@ -13,8 +15,11 @@ import javax.inject.Inject
class TextParserImpl @Inject constructor( class TextParserImpl @Inject constructor(
private val txtTextParser: TxtTextParser, private val txtTextParser: TxtTextParser,
private val pdfTextParser: PdfTextParser, private val pdfTextParser: PdfTextParser,
private val epubTextParser: EpubTextParser, private val epubTextParser: HtmlTextParser,
private val fb2TextParser: Fb2TextParser private val fb2TextParser: Fb2TextParser,
private val zipTextParser: ZipTextParser,
private val htmlTextParser: HtmlTextParser,
private val htmTextParser: HtmTextParser,
) : TextParser { ) : TextParser {
override suspend fun parse(file: File): Resource<List<String>> { override suspend fun parse(file: File): Resource<List<String>> {
if (!file.exists()) { if (!file.exists()) {
@ -23,25 +28,38 @@ class TextParserImpl @Inject constructor(
) )
} }
val fileFormat = ".${file.name.substringAfterLast(".")}".lowercase().trim() val fileFormat = ".${file.extension}".lowercase().trim()
return when (fileFormat) {
".pdf" -> {
pdfTextParser.parse(file)
}
if (fileFormat == ".pdf") { ".epub" -> {
return pdfTextParser.parse(file) epubTextParser.parse(file)
}
".txt" -> {
txtTextParser.parse(file)
}
".fb2" -> {
fb2TextParser.parse(file)
}
".zip" -> {
zipTextParser.parse(file)
}
".html" -> {
htmlTextParser.parse(file)
}
".htm" -> {
htmTextParser.parse(file)
}
else -> Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
} }
if (fileFormat == ".epub") {
return epubTextParser.parse(file)
}
if (fileFormat == ".txt") {
return txtTextParser.parse(file)
}
if (fileFormat == ".fb2") {
return fb2TextParser.parse(file)
}
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
} }
} }

View file

@ -5,6 +5,7 @@ import android.graphics.BitmapFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jsoup.Jsoup import org.jsoup.Jsoup
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.FileParser import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category import ua.acclorite.book_story.domain.model.Category
@ -36,30 +37,43 @@ class EpubFileParser @Inject constructor() : FileParser {
.use { it.readText() } .use { it.readText() }
val document = Jsoup.parse(opfContent) val document = Jsoup.parse(opfContent)
val title = document.select("metadata > dc|title").text().trim() val title = document.select("metadata > dc|title").text().trim().run {
val author = UIText.StringValue( ifBlank {
document.select("metadata > dc|creator").text().trim() file.nameWithoutExtension.trim()
) }
}
val author = document.select("metadata > dc|creator").text().trim().run {
if (isBlank()) {
UIText.StringResource(R.string.unknown_author)
} else {
UIText.StringValue(this)
}
}
val description = Jsoup.parse( val description = Jsoup.parse(
document.select("metadata > dc|description").text() document.select("metadata > dc|description").text()
).text() ).text().run {
ifBlank {
null
}
}
var coverImagePath: String? = null val coverImage = document
val coverId = document
.select("metadata > meta[name=cover]") .select("metadata > meta[name=cover]")
.attr("content") .attr("content")
if (coverId.isNotBlank()) { .run {
coverImagePath = document if (isNotBlank()) {
.select("manifest > item[id=$coverId]") document
.attr("href") .select("manifest > item[id=$this]")
} .attr("href")
.apply { if (isNotBlank()) return@run this }
}
if (coverImagePath.isNullOrBlank()) { document
coverImagePath = document .select("manifest > item[media-type*=image]")
.select("manifest > item[media-type*=image]") .firstOrNull()?.attr("href")
.firstOrNull()?.attr("href") }
}
book = Book( book = Book(
title = title, title = title,
@ -73,7 +87,7 @@ class EpubFileParser @Inject constructor() : FileParser {
lastOpened = null, lastOpened = null,
category = Category.entries[0], category = Category.entries[0],
coverImage = null coverImage = null
) to extractCoverImageBitmap(file, coverImagePath) ) to extractCoverImageBitmap(file, coverImage)
} }
} }
return book return book

View file

@ -29,7 +29,7 @@ class Fb2FileParser @Inject constructor() : FileParser {
} }
val titleFromFile = extractElementContent(document, "book-title") val titleFromFile = extractElementContent(document, "book-title")
val title = titleFromFile ?: file.name.dropLast(4).trim() val title = titleFromFile ?: file.nameWithoutExtension.trim()
val authorFirstName = extractElementContent(document, "first-name") val authorFirstName = extractElementContent(document, "first-name")
val authorLastName = extractElementContent(document, "last-name") val authorLastName = extractElementContent(document, "last-name")

View file

@ -0,0 +1,47 @@
package ua.acclorite.book_story.data.parser.htm
import org.jsoup.Jsoup
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.util.CoverImage
import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import javax.inject.Inject
class HtmFileParser @Inject constructor() : FileParser {
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
if (!file.name.endsWith(".htm", true) || !file.exists()) {
return null
}
try {
val document = Jsoup.parse(file)
val title = document.select("head > title").text().trim().run {
ifBlank {
file.nameWithoutExtension.trim()
}
}
return Book(
title = title,
author = UIText.StringResource(R.string.unknown_author),
description = null,
textPath = "",
scrollIndex = 0,
scrollOffset = 0,
progress = 0f,
filePath = file.path,
lastOpened = null,
category = Category.entries[0],
coverImage = null
) to null
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
}

View file

@ -0,0 +1,48 @@
package ua.acclorite.book_story.data.parser.htm
import org.jsoup.Jsoup
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.TextParser
import ua.acclorite.book_story.domain.util.Resource
import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import javax.inject.Inject
class HtmTextParser @Inject constructor() : TextParser {
override suspend fun parse(file: File): Resource<List<String>> {
if (!file.name.endsWith(".htm", true) || !file.exists()) {
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
}
try {
val lines = mutableListOf<String>()
val document = Jsoup.parse(file)
document.select("p").append("\n")
document
.wholeText()
.lines()
.forEach { line ->
if (line.isNotBlank()) {
lines.add(line.trim())
}
}
if (lines.isEmpty()) {
return Resource.Error(UIText.StringResource(R.string.error_file_empty))
}
return Resource.Success(lines)
} catch (e: Exception) {
e.printStackTrace()
return Resource.Error(
UIText.StringResource(
R.string.error_query,
e.message?.take(40)?.trim() ?: ""
)
)
}
}
}

View file

@ -0,0 +1,47 @@
package ua.acclorite.book_story.data.parser.html
import org.jsoup.Jsoup
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.util.CoverImage
import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import javax.inject.Inject
class HtmlFileParser @Inject constructor() : FileParser {
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
if (!file.name.endsWith(".html", true) || !file.exists()) {
return null
}
try {
val document = Jsoup.parse(file)
val title = document.select("head > title").text().trim().run {
ifBlank {
file.nameWithoutExtension.trim()
}
}
return Book(
title = title,
author = UIText.StringResource(R.string.unknown_author),
description = null,
textPath = "",
scrollIndex = 0,
scrollOffset = 0,
progress = 0f,
filePath = file.path,
lastOpened = null,
category = Category.entries[0],
coverImage = null
) to null
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
}

View file

@ -0,0 +1,48 @@
package ua.acclorite.book_story.data.parser.html
import org.jsoup.Jsoup
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.TextParser
import ua.acclorite.book_story.domain.util.Resource
import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import javax.inject.Inject
class HtmlTextParser @Inject constructor() : TextParser {
override suspend fun parse(file: File): Resource<List<String>> {
if (!file.name.endsWith(".html", true) || !file.exists()) {
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
}
try {
val lines = mutableListOf<String>()
val document = Jsoup.parse(file)
document.select("p").append("\n")
document
.wholeText()
.lines()
.forEach { line ->
if (line.isNotBlank()) {
lines.add(line.trim())
}
}
if (lines.isEmpty()) {
return Resource.Error(UIText.StringResource(R.string.error_file_empty))
}
return Resource.Success(lines)
} catch (e: Exception) {
e.printStackTrace()
return Resource.Error(
UIText.StringResource(
R.string.error_query,
e.message?.take(40)?.trim() ?: ""
)
)
}
}
}

View file

@ -24,10 +24,11 @@ class PdfFileParser @Inject constructor(private val application: Application) :
val document = PDDocument.load(file) val document = PDDocument.load(file)
val title = document.documentInformation.title ?: file.name.dropLast(4).trim() val title = document.documentInformation.title ?: file.nameWithoutExtension.trim()
val fileAuthor = document.documentInformation.author val author = document.documentInformation.author.run {
val author = if (fileAuthor != null) UIText.StringValue(fileAuthor) if (isNullOrBlank()) UIText.StringResource(R.string.unknown_author)
else UIText.StringResource(R.string.unknown_author) else UIText.StringValue(this)
}
val description = document.documentInformation.subject ?: null val description = document.documentInformation.subject ?: null
document.close() document.close()

View file

@ -17,7 +17,7 @@ class TxtFileParser @Inject constructor() : FileParser {
} }
try { try {
val title = file.name.trim().dropLast(4) val title = file.nameWithoutExtension.trim()
val author = UIText.StringResource(R.string.unknown_author) val author = UIText.StringResource(R.string.unknown_author)
return Book( return Book(

View file

@ -0,0 +1,116 @@
package ua.acclorite.book_story.data.parser.zip
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Jsoup
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.FileParser
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.util.CoverImage
import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import java.util.zip.ZipFile
import javax.inject.Inject
class ZipFileParser @Inject constructor() : FileParser {
override suspend fun parse(file: File): Pair<Book, CoverImage?>? {
if (!file.name.endsWith(".zip", true) || !file.exists()) {
return null
}
try {
var book: Pair<Book, CoverImage?>? = null
withContext(Dispatchers.IO) {
ZipFile(file).use { zip ->
val opfEntry = zip.entries().asSequence().find { entry ->
entry.name.endsWith(".opf")
} ?: return@withContext
val opfContent = zip
.getInputStream(opfEntry)
.bufferedReader()
.use { it.readText() }
val document = Jsoup.parse(opfContent)
val title = document.select("metadata > dc|title").text().trim().run {
ifBlank {
file.nameWithoutExtension.trim()
}
}
val author = document.select("metadata > dc|creator").text().trim().run {
if (isBlank()) {
UIText.StringResource(R.string.unknown_author)
} else {
UIText.StringValue(this)
}
}
val description = Jsoup.parse(
document.select("metadata > dc|description").text()
).text().run {
ifBlank {
null
}
}
val coverImage = document
.select("metadata > meta[name=cover]")
.attr("content")
.run {
if (isNotBlank()) {
document
.select("manifest > item[id=$this]")
.attr("href")
.apply { if (isNotBlank()) return@run this }
}
document
.select("manifest > item[media-type*=image]")
.firstOrNull()?.attr("href")
}
book = Book(
title = title,
author = author,
description = description,
textPath = "",
scrollIndex = 0,
scrollOffset = 0,
progress = 0f,
filePath = file.path,
lastOpened = null,
category = Category.entries[0],
coverImage = null
) to extractCoverImageBitmap(file, coverImage)
}
}
return book
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
}
private fun extractCoverImageBitmap(file: File, coverImagePath: String?): Bitmap? {
if (coverImagePath.isNullOrBlank()) {
return null
}
ZipFile(file).use { zip ->
zip.entries().asSequence().forEach { entry ->
if (entry.name.endsWith(coverImagePath)) {
val imageBytes = zip.getInputStream(entry).readBytes()
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}
}
}
return null
}

View file

@ -0,0 +1,69 @@
package ua.acclorite.book_story.data.parser.zip
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Jsoup
import ua.acclorite.book_story.R
import ua.acclorite.book_story.data.parser.TextParser
import ua.acclorite.book_story.domain.util.Resource
import ua.acclorite.book_story.domain.util.UIText
import java.io.File
import java.util.zip.ZipFile
import javax.inject.Inject
class ZipTextParser @Inject constructor() : TextParser {
override suspend fun parse(file: File): Resource<List<String>> {
if (!file.name.endsWith(".zip", true) || !file.exists()) {
return Resource.Error(UIText.StringResource(R.string.error_wrong_file_format))
}
try {
val lines = mutableListOf<String>()
withContext(Dispatchers.IO) {
ZipFile(file).use { zip ->
zip.entries().asSequence().forEach { entry ->
if (
entry.name.endsWith(".xhtml")
|| entry.name.endsWith(".html")
|| entry.name.endsWith(".xml")
|| entry.name.endsWith(".htm")
) {
val content = zip.getInputStream(entry).bufferedReader()
.use {
it.readText()
}
val document = Jsoup.parse(content)
document.select("p").append("\n")
document
.wholeText()
.lines()
.forEach { line ->
if (line.isNotBlank()) {
lines.add(line.trim())
}
}
}
}
}
}
if (lines.isEmpty()) {
return Resource.Error(UIText.StringResource(R.string.error_file_empty))
}
return Resource.Success(lines)
} catch (e: Exception) {
e.printStackTrace()
return Resource.Error(
UIText.StringResource(
R.string.error_query,
e.message?.take(40)?.trim() ?: ""
)
)
}
}
}

View file

@ -27,497 +27,521 @@ object Constants {
const val MAIN_STATE = "main_state" const val MAIN_STATE = "main_state"
// Supported file extensions // Supported file extensions
val EXTENSIONS = listOf(".epub", ".pdf", ".fb2", ".txt") val EXTENSIONS = provideSupportedExtensions()
// Supported languages // Supported languages
val LANGUAGES = listOf( val LANGUAGES = provideLanguages()
Pair("en", "English"),
Pair("uk", "Українська"),
)
// Navigation items for NavigationBars. // Navigation items for NavigationBars.
val NAVIGATION_ITEMS = listOf( val NAVIGATION_ITEMS = provideNavigationItems()
NavigationItem(
screen = Screen.Library,
title = R.string.library_screen,
tooltip = R.string.library_content_desc,
selectedIcon = R.drawable.library_screen_filled,
unselectedIcon = R.drawable.library_screen_outlined
),
NavigationItem(
screen = Screen.History,
title = R.string.history_screen,
tooltip = R.string.history_content_desc,
selectedIcon = R.drawable.history_screen_filled,
unselectedIcon = R.drawable.history_screen_outlined
),
NavigationItem(
screen = Screen.Browse,
title = R.string.browse_screen,
tooltip = R.string.browse_content_desc,
selectedIcon = R.drawable.browse_screen_filled,
unselectedIcon = R.drawable.browse_screen_outlined
)
)
// Supported themes // Supported themes
val THEMES = listOf( val THEMES = provideThemes()
Pair(Theme.DYNAMIC, UIText.StringResource(R.string.dynamic_theme)),
Pair(Theme.BLUE, UIText.StringResource(R.string.blue_theme)),
Pair(Theme.GREEN, UIText.StringResource(R.string.green_theme)),
Pair(Theme.RED, UIText.StringResource(R.string.red_theme)),
Pair(Theme.PURPLE, UIText.StringResource(R.string.purple_theme)),
Pair(Theme.PINK, UIText.StringResource(R.string.pink_theme)),
Pair(Theme.YELLOW, UIText.StringResource(R.string.yellow_theme)),
Pair(Theme.AQUA, UIText.StringResource(R.string.aqua_theme)),
)
// Credits // Credits
val CREDITS = listOf( val CREDITS = provideCredits()
Credit(
name = "Tachiyomi (Mihon)",
source = "GitHub",
credits = listOf(
UIText.StringResource(R.string.credits_design),
UIText.StringValue("Readme")
),
website = "https://www.github.com/mihonapp/mihon"
),
Credit(
name = "Kitsune",
source = "GitHub",
credits = listOf(
UIText.StringResource(R.string.credits_updates)
),
website = "https://www.github.com/Drumber/Kitsune"
),
Credit(
name = "Material Design Icons",
source = "Google Fonts",
credits = listOf(
UIText.StringResource(R.string.credits_icon)
),
website = "https://fonts.google.com/icons"
),
)
// Help Tips // Help Tips
val HELP_TIPS = listOf( val HELP_TIPS = provideHelpTips()
HelpTip(
title = R.string.help_title_how_to_find_books,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_find_books))
},
customContent = { onHelpEvent ->
HelpFindBooksContent(onEvent = onHelpEvent)
}
),
HelpTip(
title = R.string.help_title_how_to_add_books,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_add_books_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Browse, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_add_books_2))
}
append(". ")
append(stringResource(id = R.string.help_desc_how_to_add_books_3) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Library, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_add_books_4))
}
append(".")
}
),
HelpTip(
title = R.string.help_title_how_to_customize_app,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_customize_app_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Settings, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_customize_app_2))
}
append(". ")
append(stringResource(id = R.string.help_desc_how_to_customize_app_3))
}
),
HelpTip(
title = R.string.help_title_how_to_move_or_delete_books,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Library, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_2))
}
append(". ")
append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_3))
}
),
HelpTip(
title = R.string.help_title_how_to_edit_book,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_edit_book_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Library, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_edit_book_2))
}
append(" ")
append(stringResource(id = R.string.help_desc_how_to_edit_book_3))
}
),
HelpTip(
title = R.string.help_title_how_to_read_book,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_read_book_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Library, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_read_book_2))
}
append(". ")
append(stringResource(id = R.string.help_desc_how_to_read_book_3))
}
),
HelpTip(
title = R.string.help_title_how_to_customize_reader,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_customize_reader_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Settings, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_customize_reader_2))
}
append(" ")
append(stringResource(id = R.string.help_desc_how_to_customize_reader_3))
}
),
HelpTip(
title = R.string.help_title_how_to_update_book,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_update_book_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Library, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_update_book_2))
}
append(" ")
append(stringResource(id = R.string.help_desc_how_to_update_book_3))
}
),
HelpTip(
title = R.string.help_title_how_to_manage_history,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_manage_history_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.History, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_manage_history_2))
}
append(". ")
append(stringResource(id = R.string.help_desc_how_to_manage_history_3))
}
),
HelpTip(
title = R.string.help_title_how_to_use_tooltip,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_use_tooltip_1))
}
),
HelpTip(
title = R.string.help_title_how_to_use_double_click_translation,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_use_double_click_translation_1))
}
),
HelpTip(
title = R.string.help_title_how_to_create_color_presets,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_create_color_presets_1))
}
),
HelpTip(
title = R.string.help_title_how_to_use_color_presets,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_use_color_presets_1))
}
),
)
// About Badges // About Badges
val ABOUT_BADGES = listOf( val ABOUT_BADGES = provideAboutBadges()
Badge(
id = "x",
drawable = R.drawable.x_logo,
imageVector = null,
contentDescription = R.string.x_content_desc,
url = "https://www.x.com/acclorite"
),
Badge(
id = "reddit",
drawable = R.drawable.reddit,
imageVector = null,
contentDescription = R.string.reddit_content_desc,
url = "https://www.reddit.com/user/Acclorite/"
),
Badge(
id = "tryzub",
drawable = R.drawable.tryzub,
imageVector = null,
contentDescription = R.string.tryzub_content_desc,
url = null
),
Badge(
id = "github_project",
drawable = R.drawable.github,
imageVector = null,
contentDescription = R.string.github_project_content_desc,
url = "https://www.github.com/Acclorite/book-story"
),
Badge(
id = "github_profile",
drawable = null,
imageVector = Icons.Default.Person,
contentDescription = R.string.github_profile_content_desc,
url = "https://www.github.com/Acclorite"
),
)
// Fonts for Reader // Fonts for Reader
val FONTS = listOf( val FONTS = provideFonts()
FontWithName(
"default", // Empty Book (Used for Default Screen values)
UIText.StringResource(R.string.default_font), val EMPTY_BOOK = provideEmptyBook()
FontFamily.Default
// Default Color Preset (Used when creating new color presets)
val DEFAULT_COLOR_PRESET = provideDefaultColorPreset()
}
private fun provideSupportedExtensions() = listOf(
".epub", ".pdf", ".fb2", ".txt", ".zip", ".html", ".htm"
)
private fun provideLanguages() = listOf(
Pair("en", "English"),
Pair("uk", "Українська"),
)
private fun provideNavigationItems() = listOf(
NavigationItem(
screen = Screen.Library,
title = R.string.library_screen,
tooltip = R.string.library_content_desc,
selectedIcon = R.drawable.library_screen_filled,
unselectedIcon = R.drawable.library_screen_outlined
),
NavigationItem(
screen = Screen.History,
title = R.string.history_screen,
tooltip = R.string.history_content_desc,
selectedIcon = R.drawable.history_screen_filled,
unselectedIcon = R.drawable.history_screen_outlined
),
NavigationItem(
screen = Screen.Browse,
title = R.string.browse_screen,
tooltip = R.string.browse_content_desc,
selectedIcon = R.drawable.browse_screen_filled,
unselectedIcon = R.drawable.browse_screen_outlined
)
)
private fun provideThemes() = listOf(
Pair(Theme.DYNAMIC, UIText.StringResource(R.string.dynamic_theme)),
Pair(Theme.BLUE, UIText.StringResource(R.string.blue_theme)),
Pair(Theme.GREEN, UIText.StringResource(R.string.green_theme)),
Pair(Theme.RED, UIText.StringResource(R.string.red_theme)),
Pair(Theme.PURPLE, UIText.StringResource(R.string.purple_theme)),
Pair(Theme.PINK, UIText.StringResource(R.string.pink_theme)),
Pair(Theme.YELLOW, UIText.StringResource(R.string.yellow_theme)),
Pair(Theme.AQUA, UIText.StringResource(R.string.aqua_theme)),
)
private fun provideCredits() = listOf(
Credit(
name = "Tachiyomi (Mihon)",
source = "GitHub",
credits = listOf(
UIText.StringResource(R.string.credits_design),
UIText.StringValue("Readme")
), ),
FontWithName( website = "https://www.github.com/mihonapp/mihon"
"raleway", ),
UIText.StringValue("Raleway"), Credit(
FontFamily( name = "Kitsune",
Font(R.font.raleway_regular), source = "GitHub",
Font(R.font.raleway_regular_italic, style = FontStyle.Italic) credits = listOf(
) UIText.StringResource(R.string.credits_updates)
), ),
FontWithName( website = "https://www.github.com/Drumber/Kitsune"
"open_sans", ),
UIText.StringValue("Open Sans"), Credit(
FontFamily( name = "Material Design Icons",
Font(R.font.opensans_regular), source = "Google Fonts",
Font(R.font.opensans_regular_italic, style = FontStyle.Italic) credits = listOf(
) UIText.StringResource(R.string.credits_icon)
), ),
FontWithName( website = "https://fonts.google.com/icons"
"mulish", ),
UIText.StringValue("Mulish"), )
FontFamily(
Font(R.font.mulish_regular), private fun provideHelpTips() = listOf(
Font(R.font.mulish_regular_italic, style = FontStyle.Italic) HelpTip(
) title = R.string.help_title_how_to_find_books,
), description = { _, _ ->
FontWithName( append(stringResource(id = R.string.help_desc_how_to_find_books))
"arimo", },
UIText.StringValue("Arimo"), customContent = { onHelpEvent ->
FontFamily( HelpFindBooksContent(onEvent = onHelpEvent)
Font(R.font.arimo_regular), }
Font(R.font.arimo_regular_italic, style = FontStyle.Italic) ),
)
), HelpTip(
FontWithName( title = R.string.help_title_how_to_add_books,
"garamond", description = { onNavigate, fromStart ->
UIText.StringValue("Garamond"), append(stringResource(id = R.string.help_desc_how_to_add_books_1) + " ")
FontFamily(
Font(R.font.garamond_regular), HelpAnnotation(
Font(R.font.garamond_regular_italic, style = FontStyle.Italic) onClick = {
) if (!fromStart) {
), onNavigate {
FontWithName( navigate(Screen.Browse, useBackAnimation = true)
"roboto_serif", }
UIText.StringValue("Roboto Serif"), }
FontFamily( }
Font(R.font.robotoserif_regular), ) {
Font(R.font.robotoserif_regular_italic, style = FontStyle.Italic) append(stringResource(id = R.string.help_desc_how_to_add_books_2))
) }
), append(". ")
FontWithName(
"noto_serif", append(stringResource(id = R.string.help_desc_how_to_add_books_3) + " ")
UIText.StringValue("Noto Serif"),
FontFamily( HelpAnnotation(
Font(R.font.notoserif_regular), onClick = {
Font(R.font.notoserif_regular_italic, style = FontStyle.Italic) if (!fromStart) {
) onNavigate {
), navigate(Screen.Library, useBackAnimation = true)
FontWithName( }
"noto_sans", }
UIText.StringValue("Noto Sans"), }
FontFamily( ) {
Font(R.font.notosans_regular), append(stringResource(id = R.string.help_desc_how_to_add_books_4))
Font(R.font.notosans_regular_italic, style = FontStyle.Italic) }
) append(".")
), }
FontWithName( ),
"roboto",
UIText.StringValue("Roboto"), HelpTip(
FontFamily( title = R.string.help_title_how_to_customize_app,
Font(R.font.roboto_regular), description = { onNavigate, fromStart ->
Font(R.font.roboto_regular_italic, style = FontStyle.Italic) append(stringResource(id = R.string.help_desc_how_to_customize_app_1) + " ")
)
), HelpAnnotation(
FontWithName( onClick = {
"jost", if (!fromStart) {
UIText.StringValue("Jost"), onNavigate {
FontFamily( navigate(Screen.Settings, useBackAnimation = true)
Font(R.font.jost_regular), }
Font(R.font.jost_regular_italic, style = FontStyle.Italic) }
) }
), ) {
FontWithName( append(stringResource(id = R.string.help_desc_how_to_customize_app_2))
"merriweather", }
UIText.StringValue("Merriweather"), append(". ")
FontFamily(
Font(R.font.merriweather_regular), append(stringResource(id = R.string.help_desc_how_to_customize_app_3))
Font(R.font.merriweather_regular_italic, style = FontStyle.Italic) }
) ),
),
FontWithName( HelpTip(
"montserrat", title = R.string.help_title_how_to_move_or_delete_books,
UIText.StringValue("Montserrat"), description = { onNavigate, fromStart ->
FontFamily( append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_1) + " ")
Font(R.font.montserrat_regular),
Font(R.font.montserrat_regular_italic, style = FontStyle.Italic) HelpAnnotation(
) onClick = {
), if (!fromStart) {
FontWithName( onNavigate {
"nunito", navigate(Screen.Library, useBackAnimation = true)
UIText.StringValue("Nunito"), }
FontFamily( }
Font(R.font.nunito_regular), }
Font(R.font.nunito_regular_italic, style = FontStyle.Italic) ) {
) append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_2))
), }
FontWithName( append(". ")
"roboto_slab",
UIText.StringValue("Roboto Slab"), append(stringResource(id = R.string.help_desc_how_to_move_or_delete_books_3))
FontFamily( }
Font(R.font.robotoslab_regular) ),
)
), HelpTip(
FontWithName( title = R.string.help_title_how_to_edit_book,
"lora", description = { onNavigate, fromStart ->
UIText.StringValue("Lora"), append(stringResource(id = R.string.help_desc_how_to_edit_book_1) + " ")
FontFamily(
Font(R.font.lora_regular), HelpAnnotation(
Font(R.font.lora_regular_italic, style = FontStyle.Italic) onClick = {
) if (!fromStart) {
onNavigate {
navigate(Screen.Library, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_edit_book_2))
}
append(" ")
append(stringResource(id = R.string.help_desc_how_to_edit_book_3))
}
),
HelpTip(
title = R.string.help_title_how_to_read_book,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_read_book_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Library, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_read_book_2))
}
append(". ")
append(stringResource(id = R.string.help_desc_how_to_read_book_3))
}
),
HelpTip(
title = R.string.help_title_how_to_customize_reader,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_customize_reader_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Settings, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_customize_reader_2))
}
append(" ")
append(stringResource(id = R.string.help_desc_how_to_customize_reader_3))
}
),
HelpTip(
title = R.string.help_title_how_to_update_book,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_update_book_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.Library, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_update_book_2))
}
append(" ")
append(stringResource(id = R.string.help_desc_how_to_update_book_3))
}
),
HelpTip(
title = R.string.help_title_how_to_manage_history,
description = { onNavigate, fromStart ->
append(stringResource(id = R.string.help_desc_how_to_manage_history_1) + " ")
HelpAnnotation(
onClick = {
if (!fromStart) {
onNavigate {
navigate(Screen.History, useBackAnimation = true)
}
}
}
) {
append(stringResource(id = R.string.help_desc_how_to_manage_history_2))
}
append(". ")
append(stringResource(id = R.string.help_desc_how_to_manage_history_3))
}
),
HelpTip(
title = R.string.help_title_how_to_use_tooltip,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_use_tooltip_1))
}
),
HelpTip(
title = R.string.help_title_how_to_use_double_click_translation,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_use_double_click_translation_1))
}
),
HelpTip(
title = R.string.help_title_how_to_create_color_presets,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_create_color_presets_1))
}
),
HelpTip(
title = R.string.help_title_how_to_use_color_presets,
description = { _, _ ->
append(stringResource(id = R.string.help_desc_how_to_use_color_presets_1))
}
),
)
private fun provideAboutBadges() = listOf(
Badge(
id = "x",
drawable = R.drawable.x_logo,
imageVector = null,
contentDescription = R.string.x_content_desc,
url = "https://www.x.com/acclorite"
),
Badge(
id = "reddit",
drawable = R.drawable.reddit,
imageVector = null,
contentDescription = R.string.reddit_content_desc,
url = "https://www.reddit.com/user/Acclorite/"
),
Badge(
id = "tryzub",
drawable = R.drawable.tryzub,
imageVector = null,
contentDescription = R.string.tryzub_content_desc,
url = null
),
Badge(
id = "github_project",
drawable = R.drawable.github,
imageVector = null,
contentDescription = R.string.github_project_content_desc,
url = "https://www.github.com/Acclorite/book-story"
),
Badge(
id = "github_profile",
drawable = null,
imageVector = Icons.Default.Person,
contentDescription = R.string.github_profile_content_desc,
url = "https://www.github.com/Acclorite"
),
)
private fun provideFonts() = listOf(
FontWithName(
"default",
UIText.StringResource(R.string.default_font),
FontFamily.Default
),
FontWithName(
"raleway",
UIText.StringValue("Raleway"),
FontFamily(
Font(R.font.raleway_regular),
Font(R.font.raleway_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"open_sans",
UIText.StringValue("Open Sans"),
FontFamily(
Font(R.font.opensans_regular),
Font(R.font.opensans_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"mulish",
UIText.StringValue("Mulish"),
FontFamily(
Font(R.font.mulish_regular),
Font(R.font.mulish_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"arimo",
UIText.StringValue("Arimo"),
FontFamily(
Font(R.font.arimo_regular),
Font(R.font.arimo_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"garamond",
UIText.StringValue("Garamond"),
FontFamily(
Font(R.font.garamond_regular),
Font(R.font.garamond_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"roboto_serif",
UIText.StringValue("Roboto Serif"),
FontFamily(
Font(R.font.robotoserif_regular),
Font(R.font.robotoserif_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"noto_serif",
UIText.StringValue("Noto Serif"),
FontFamily(
Font(R.font.notoserif_regular),
Font(R.font.notoserif_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"noto_sans",
UIText.StringValue("Noto Sans"),
FontFamily(
Font(R.font.notosans_regular),
Font(R.font.notosans_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"roboto",
UIText.StringValue("Roboto"),
FontFamily(
Font(R.font.roboto_regular),
Font(R.font.roboto_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"jost",
UIText.StringValue("Jost"),
FontFamily(
Font(R.font.jost_regular),
Font(R.font.jost_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"merriweather",
UIText.StringValue("Merriweather"),
FontFamily(
Font(R.font.merriweather_regular),
Font(R.font.merriweather_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"montserrat",
UIText.StringValue("Montserrat"),
FontFamily(
Font(R.font.montserrat_regular),
Font(R.font.montserrat_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"nunito",
UIText.StringValue("Nunito"),
FontFamily(
Font(R.font.nunito_regular),
Font(R.font.nunito_regular_italic, style = FontStyle.Italic)
)
),
FontWithName(
"roboto_slab",
UIText.StringValue("Roboto Slab"),
FontFamily(
Font(R.font.robotoslab_regular)
)
),
FontWithName(
"lora",
UIText.StringValue("Lora"),
FontFamily(
Font(R.font.lora_regular),
Font(R.font.lora_regular_italic, style = FontStyle.Italic)
) )
) )
)
val EMPTY_BOOK = Book( private fun provideEmptyBook() = Book(
id = -1, id = -1,
title = "", title = "",
author = UIText.StringValue(""), author = UIText.StringValue(""),
description = null, description = null,
textPath = "", textPath = "",
filePath = "", filePath = "",
coverImage = null, coverImage = null,
scrollIndex = 0, scrollIndex = 0,
scrollOffset = 0, scrollOffset = 0,
progress = 0f, progress = 0f,
lastOpened = null, lastOpened = null,
category = Category.READING category = Category.READING
) )
val DEFAULT_COLOR_PRESET = ColorPreset( private fun provideDefaultColorPreset() = ColorPreset(
id = -1, id = -1,
name = null, name = null,
backgroundColor = Color.DarkGray, backgroundColor = Color.DarkGray,
fontColor = Color.LightGray, fontColor = Color.LightGray,
isSelected = false isSelected = false
) )
}