Epub reader themes (#97)

* Implemented a custom theme engine for the EPUB reader, allowing users to switch between built-in themes (System, Light, Dark, Sepia, Slate, and OLED).

Key changes include:
- Added `ReaderThemePanel` and theme selection logic to `EpubReaderScreen`.
- Introduced a theme persistence layer using SharedPreferences.
- Updated `ChapterWebView` and `epub_reader.js` to dynamically apply background and text colors via JavaScript, including improved contrast adjustment for inline styles.
- Enhanced `CssParser` and `ContentStyler` in the paginated reader to adapt CSS colors based on the active theme's background and text luminance.
- Integrated theme-specific background and text colors into `PaginatedReaderScreen` and its layout components.
- Added a theme palette icon to the reader controls.

* Added custom theme builder and texture support to EPUB reader

* Refactored color picker(PDF) into shared components and enhanced EPUB theme editor

* Updated theme customization UI and improved WebView theme application efficiency

* Updated page flip animation to use paper color with a theme-based tint

* Improved state restoration during paginated reader reconfiguration
This commit is contained in:
Aryan 2026-03-19 19:59:51 +05:30 committed by GitHub
parent eaf0d4af00
commit c9dc3ce8c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1817 additions and 847 deletions

View file

@ -50,6 +50,8 @@ class ContentStyler(
private val fontFamilyMap: Map<String, FontFamily>,
private val density: Density,
private val isDarkTheme: Boolean,
private val themeBackgroundColor: Color,
private val themeTextColor: Color,
private val chapterAbsPath: String,
private val extractionBasePath: String,
private val userTextAlign: TextAlign?
@ -59,7 +61,6 @@ class ContentStyler(
return groupFloatingBlocks(semanticBlocks.mapNotNull { styleBlock(it) })
}
// ADD this function to group floating blocks, similar to the original parser
private fun groupFloatingBlocks(blocks: List<ContentBlock>): List<ContentBlock> {
if (blocks.isEmpty()) return emptyList()
@ -71,7 +72,6 @@ class ContentStyler(
val floatDirection = (currentBlock as? ImageBlock)?.style?.float
if (currentBlock is ImageBlock && floatDirection in listOf("left", "right")) {
val floatedImage = currentBlock
val paragraphsToWrap = mutableListOf<ParagraphBlock>()
while (processingQueue.isNotEmpty()) {
@ -86,11 +86,11 @@ class ContentStyler(
}
}
val wrappingBlock = WrappingContentBlock(
floatedImage,
currentBlock,
paragraphsToWrap,
elementId = floatedImage.elementId,
cfi = floatedImage.cfi,
blockIndex = floatedImage.blockIndex
elementId = currentBlock.elementId,
cfi = currentBlock.cfi,
blockIndex = currentBlock.blockIndex
)
result.add(wrappingBlock)
} else {
@ -208,7 +208,7 @@ class ContentStyler(
private fun applyThemeToStyle(style: CssStyle): CssStyle {
val newSpanStyle = style.spanStyle.let { original ->
val newColor = if (original.color.isSpecified) {
CssParser.adaptColorForTheme(original.color, isDarkTheme, isBackground = false)
CssParser.adaptColorForTheme(original.color, isDarkTheme, isBackground = false, themeBackgroundColor, themeTextColor)
} else {
original.color
}
@ -217,14 +217,14 @@ class ContentStyler(
val newBlockStyle = style.blockStyle.let { original ->
val newBgColor = if (original.backgroundColor.isSpecified) {
CssParser.adaptColorForTheme(original.backgroundColor, isDarkTheme, isBackground = true)
CssParser.adaptColorForTheme(original.backgroundColor, isDarkTheme, isBackground = true, themeBackgroundColor, themeTextColor)
} else {
original.backgroundColor
}
fun themeBorder(b: BorderStyle?): BorderStyle? {
if (b == null) return null
val newColor = CssParser.adaptColorForTheme(b.color, isDarkTheme, isBackground = false)
val newColor = CssParser.adaptColorForTheme(b.color, isDarkTheme, isBackground = false, themeBackgroundColor, themeTextColor)
return b.copy(color = newColor)
}
@ -474,7 +474,7 @@ class ContentStyler(
}
private fun toRoman(number: Int): String {
if (number < 1 || number > 3999) return number.toString()
if (number !in 1..3999) return number.toString()
val values = listOf(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1)
val symbols = listOf("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I")
val result = StringBuilder()