General improvements (#316)

* Added support for right text alignment in epub reader

* Added tabs management to PDF navigation drawer

* Implemented unified selection menu placement logic

* Added reset functionality for toolbar customization

* Improved highlight filtering in epub pagination

* Migrated hardcoded UI strings to string resources

* Extracted desktop application logic from Main.kt into modular files

* Refactored Main.kt by extracting PDF and EPUB logic into specialized files

* Added Spanish language support

* Added system default option to app language selection
This commit is contained in:
Aryan 2026-05-16 16:19:31 +05:30 committed by GitHub
parent 759d4b73a0
commit 056485a140
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 9184 additions and 5947 deletions

View file

@ -0,0 +1,60 @@
package com.aryan.reader
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
import org.junit.Assert.assertEquals
import org.junit.Test
class AppLanguageOptionsTest {
@Test
fun `supported app languages include Spanish`() {
assertEquals(
listOf("en", "ar", "de", "tr", "fr", "ru", "es"),
supportedAppLanguageOptions.mapNotNull { it.tag }
)
assertEquals(R.string.language_spanish, supportedAppLanguageOptions.last().labelRes)
}
@Test
fun `app language selection defaults to system before explicit overrides`() {
assertEquals(null, appLanguageSelectionOptions.first().tag)
assertEquals(R.string.language_system_default, appLanguageSelectionOptions.first().labelRes)
assertEquals(supportedAppLanguageOptions, appLanguageSelectionOptions.drop(1))
}
@Test
fun `supported app language tags are unique`() {
val tags = supportedAppLanguageOptions.mapNotNull { it.tag }
assertEquals(tags.distinct(), tags)
}
@Test
fun `supported app languages match Android locale config`() {
assertEquals(readLocaleConfigTags(), supportedAppLanguageOptions.map { it.tag })
}
private fun readLocaleConfigTags(): List<String> {
val localeConfig = listOf(
File("src/main/res/xml/locales_config.xml"),
File("app/src/main/res/xml/locales_config.xml")
).first { it.isFile }
val document = DocumentBuilderFactory.newInstance()
.apply { isNamespaceAware = true }
.newDocumentBuilder()
.parse(localeConfig)
val localeNodes = document.getElementsByTagName("locale")
return buildList {
for (index in 0 until localeNodes.length) {
add(
localeNodes.item(index)
.attributes
.getNamedItemNS("http://schemas.android.com/apk/res/android", "name")
.nodeValue
)
}
}
}
}

View file

@ -198,4 +198,29 @@ class EpubReaderBridgeAndControlsTest {
assertTrue(ReaderTool.entries.any { it.category == "Overflow Menu" })
assertEquals("Top Bar", ReaderTool.SCREEN_ORIENTATION.category)
}
@Test
fun `reader toolbar reset defaults match first-run toolbar defaults`() {
assertEquals(setOf(ReaderTool.SCREEN_ORIENTATION.name), defaultReaderHiddenTools())
assertEquals(ReaderTool.entries.toList(), defaultReaderToolOrder())
assertEquals(
ReaderTool.entries.filter { it.category == "Bottom Bar" }.map { it.name }.toSet(),
defaultReaderBottomTools()
)
val defaultItems = buildReaderToolbarItems(
hiddenTools = defaultReaderHiddenTools(),
toolOrder = defaultReaderToolOrder(),
bottomTools = defaultReaderBottomTools()
)
assertEquals(
ToolbarSection.HIDDEN,
defaultItems.single { it.tool == ReaderTool.SCREEN_ORIENTATION }.section
)
assertEquals(
ToolbarSection.BOTTOM,
defaultItems.single { it.tool == ReaderTool.SLIDER }.section
)
}
}

View file

@ -57,7 +57,7 @@ class EpubReaderPreferencesAndAnnotationsTest {
verticalMargin = 0.4f,
fontFamily = ReaderFont.LORA,
customFontPath = null,
textAlign = ReaderTextAlign.JUSTIFY
textAlign = ReaderTextAlign.RIGHT
)
saveLocalReaderSettings(
context = context,
@ -78,7 +78,7 @@ class EpubReaderPreferencesAndAnnotationsTest {
assertEquals(1.4f, global.fontSize, 0.0001f)
assertEquals(ReaderFont.LORA, global.font)
assertEquals(ReaderTextAlign.JUSTIFY, global.textAlign)
assertEquals(ReaderTextAlign.RIGHT, global.textAlign)
assertNull(global.customPath)
assertEquals(0.9f, local.fontSize, 0.0001f)
assertEquals(1.1f, local.lineHeight, 0.0001f)

View file

@ -54,6 +54,35 @@ class PaginatedHighlightMappingTest {
assertNull(getHighlightOffsetsInBlock(block, highlight))
}
@Test
fun `paginated page highlights are scoped to page chapter`() {
val chapterFourHighlight = highlight(
cfi = "/4/10:11|/4/12:79",
text = "Original chapter text",
chapterIndex = 4
)
val chapterFiveHighlight = highlight(
cfi = "/4/10:11|/4/12:79",
text = "Different chapter text",
chapterIndex = 5
)
assertEquals(
listOf(chapterFiveHighlight),
highlightsForPaginatedPage(
pageChapterIndex = 5,
userHighlights = listOf(chapterFourHighlight, chapterFiveHighlight)
)
)
assertEquals(
emptyList<UserHighlight>(),
highlightsForPaginatedPage(
pageChapterIndex = null,
userHighlights = listOf(chapterFourHighlight)
)
)
}
private fun paragraph(
text: String,
cfi: String,
@ -70,14 +99,15 @@ class PaginatedHighlightMappingTest {
private fun highlight(
cfi: String,
text: String
text: String,
chapterIndex: Int = 0
): UserHighlight {
return UserHighlight(
id = "highlight",
cfi = cfi,
text = text,
color = HighlightColor.YELLOW,
chapterIndex = 0
chapterIndex = chapterIndex
)
}
}

View file

@ -160,4 +160,36 @@ class PdfReaderSettingsAndSharedModelsTest {
assertTrue(width >= 1)
assertTrue(height >= 1)
}
@Test
fun `pdf toolbar reset defaults match first-run toolbar defaults`() {
assertEquals(
setOf(PdfReaderTool.SCREEN_ORIENTATION.name, PdfReaderTool.HIGHLIGHT_ALL.name),
defaultPdfHiddenTools()
)
assertEquals(PdfReaderTool.entries.toList(), defaultPdfToolOrder())
assertEquals(
PdfReaderTool.entries.filter { it.category == "Bottom Bar" }.map { it.name }.toSet(),
defaultPdfBottomTools()
)
val defaultItems = buildPdfToolbarItems(
hiddenTools = defaultPdfHiddenTools(),
toolOrder = defaultPdfToolOrder(),
bottomTools = defaultPdfBottomTools()
)
assertEquals(
PdfToolbarSection.HIDDEN,
defaultItems.single { it.tool == PdfReaderTool.SCREEN_ORIENTATION }.section
)
assertEquals(
PdfToolbarSection.HIDDEN,
defaultItems.single { it.tool == PdfReaderTool.HIGHLIGHT_ALL }.section
)
assertEquals(
PdfToolbarSection.BOTTOM,
defaultItems.single { it.tool == PdfReaderTool.SLIDER }.section
)
}
}