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:
parent
eaf0d4af00
commit
c9dc3ce8c2
16 changed files with 1817 additions and 847 deletions
|
|
@ -19,9 +19,6 @@
|
|||
*/
|
||||
package com.aryan.reader.paginatedreader
|
||||
|
||||
import android.os.Build
|
||||
import timber.log.Timber
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.isSpecified
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
|
|
@ -38,6 +35,7 @@ import androidx.compose.ui.unit.TextUnit
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.sp
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.roundToInt
|
||||
|
|
@ -67,24 +65,64 @@ object CssParser {
|
|||
"thick" to 5.dp
|
||||
)
|
||||
|
||||
internal fun adaptColorForTheme(color: Color, isDarkTheme: Boolean, isBackground: Boolean): Color {
|
||||
internal fun adaptColorForTheme(
|
||||
color: Color,
|
||||
isDarkTheme: Boolean,
|
||||
isBackground: Boolean,
|
||||
themeBackground: Color = Color.Unspecified,
|
||||
themeText: Color = Color.Unspecified
|
||||
): Color {
|
||||
if (!color.isSpecified) return color
|
||||
if (color.alpha < 0.9f) return color
|
||||
if (color.alpha < 0.9f && color != Color.Transparent) return color
|
||||
if (color == Color.Transparent) return color
|
||||
|
||||
val luminance = color.luminance()
|
||||
|
||||
return if (isDarkTheme) {
|
||||
if (isBackground) {
|
||||
if (luminance > 0.9) Color.Transparent else color
|
||||
if (!themeBackground.isSpecified || !themeText.isSpecified) {
|
||||
val luminance = color.luminance()
|
||||
return if (isDarkTheme) {
|
||||
if (isBackground) {
|
||||
if (luminance > 0.9) Color.Transparent else color
|
||||
} else {
|
||||
if (luminance < 0.2) Color.White.copy(alpha = 0.87f) else color
|
||||
}
|
||||
} else {
|
||||
if (luminance < 0.2) Color.White.copy(alpha = 0.87f) else color
|
||||
if (isBackground) {
|
||||
if (luminance < 0.1) Color.Transparent else color
|
||||
} else {
|
||||
if (luminance > 0.8) Color.Black.copy(alpha = 0.87f) else color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bgLuminance = themeBackground.luminance()
|
||||
val colorLuminance = color.luminance()
|
||||
|
||||
val l1 = maxOf(bgLuminance, colorLuminance)
|
||||
val l2 = minOf(bgLuminance, colorLuminance)
|
||||
val contrast = (l1 + 0.05f) / (l2 + 0.05f)
|
||||
|
||||
if (isBackground) {
|
||||
return if (isDarkTheme && colorLuminance > 0.5f) {
|
||||
Color.Transparent
|
||||
} else if (!isDarkTheme && colorLuminance < 0.2f) {
|
||||
Color.Transparent
|
||||
} else {
|
||||
color
|
||||
}
|
||||
} else {
|
||||
if (isBackground) {
|
||||
if (luminance < 0.1) Color.Transparent else color
|
||||
} else {
|
||||
if (luminance > 0.8) Color.Black.copy(alpha = 0.87f) else color
|
||||
if (contrast >= 4.5f) {
|
||||
return color
|
||||
}
|
||||
|
||||
val hsl = FloatArray(3)
|
||||
androidx.core.graphics.ColorUtils.colorToHSL(color.toArgb(), hsl)
|
||||
|
||||
if (bgLuminance < 0.5f) {
|
||||
hsl[2] = hsl[2].coerceAtLeast(0.7f)
|
||||
} else {
|
||||
hsl[2] = hsl[2].coerceAtMost(0.3f)
|
||||
}
|
||||
|
||||
return Color(androidx.core.graphics.ColorUtils.HSLToColor(hsl))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +176,9 @@ object CssParser {
|
|||
baseFontSizeSp: Float,
|
||||
density: Float,
|
||||
constraints: Constraints,
|
||||
isDarkTheme: Boolean
|
||||
isDarkTheme: Boolean,
|
||||
themeBackgroundColor: Color = Color.Unspecified,
|
||||
themeTextColor: Color = Color.Unspecified
|
||||
): OptimizedCssParseResult {
|
||||
val byTag = mutableMapOf<String, MutableList<CssRule>>()
|
||||
val byClass = mutableMapOf<String, MutableList<CssRule>>()
|
||||
|
|
@ -192,11 +232,11 @@ object CssParser {
|
|||
val specificity = calculateSpecificity(originalSelector)
|
||||
val normalStyle = parseProperties(
|
||||
propertiesGroup, baseFontSizeSp, density, constraints, onlyImportant = false,
|
||||
isDarkTheme
|
||||
isDarkTheme, themeBackgroundColor, themeTextColor
|
||||
)
|
||||
val importantStyle = parseProperties(
|
||||
propertiesGroup, baseFontSizeSp, density, constraints, onlyImportant = true,
|
||||
isDarkTheme
|
||||
isDarkTheme, themeBackgroundColor, themeTextColor
|
||||
)
|
||||
|
||||
fun addRule(style: CssStyle, spec: Int) {
|
||||
|
|
@ -335,7 +375,9 @@ object CssParser {
|
|||
density: Float,
|
||||
constraints: Constraints,
|
||||
onlyImportant: Boolean,
|
||||
isDarkTheme: Boolean
|
||||
isDarkTheme: Boolean,
|
||||
themeBackgroundColor: Color = Color.Unspecified,
|
||||
themeTextColor: Color = Color.Unspecified
|
||||
): CssStyle {
|
||||
var spanStyle = SpanStyle()
|
||||
var paragraphStyle = ParagraphStyle()
|
||||
|
|
@ -427,7 +469,7 @@ object CssParser {
|
|||
styleStr: String?
|
||||
) {
|
||||
val parsedWidth = widthStr?.let { parseCssSizeToDp(it, baseFontSizeSp, density, containerWidthPx) } ?: 0.dp
|
||||
val parsedColor = colorStr?.let { parseColor(it) }?.let { this@CssParser.adaptColorForTheme(it, isDarkTheme, isBackground = false) }
|
||||
val parsedColor = colorStr?.let { parseColor(it) }?.let { this@CssParser.adaptColorForTheme(it, isDarkTheme, isBackground = false, themeBackgroundColor, themeTextColor) }
|
||||
|
||||
val isExplicitWidth = widthStr != null
|
||||
|
||||
|
|
@ -482,7 +524,7 @@ object CssParser {
|
|||
}
|
||||
"color" -> {
|
||||
parseColor(value)?.let {
|
||||
spanStyle = spanStyle.copy(color = this@CssParser.adaptColorForTheme(it, isDarkTheme, isBackground = false))
|
||||
spanStyle = spanStyle.copy(color = this@CssParser.adaptColorForTheme(it, isDarkTheme, isBackground = false, themeBackgroundColor, themeTextColor))
|
||||
}
|
||||
}
|
||||
"text-align" -> {
|
||||
|
|
@ -587,7 +629,7 @@ object CssParser {
|
|||
|
||||
"background-color" -> {
|
||||
val originalColor = parseColor(value) ?: Color.Unspecified
|
||||
backgroundColor = this@CssParser.adaptColorForTheme(originalColor, isDarkTheme, isBackground = true)
|
||||
backgroundColor = this@CssParser.adaptColorForTheme(originalColor, isDarkTheme, isBackground = true, themeBackgroundColor, themeTextColor)
|
||||
}
|
||||
|
||||
// Border Properties
|
||||
|
|
@ -727,7 +769,7 @@ object CssParser {
|
|||
textEmphasisStyleString = value
|
||||
}
|
||||
"text-emphasis-color", "-epub-text-emphasis-color" -> {
|
||||
textEmphasisColor = parseColor(value)?.let { this@CssParser.adaptColorForTheme(it, isDarkTheme, isBackground = false) }
|
||||
textEmphasisColor = parseColor(value)?.let { this@CssParser.adaptColorForTheme(it, isDarkTheme, isBackground = false, themeBackgroundColor, themeTextColor) }
|
||||
}
|
||||
"text-emphasis-position", "-epub-text-emphasis-position" -> {
|
||||
if (value in listOf("over", "under")) {
|
||||
|
|
@ -785,7 +827,7 @@ object CssParser {
|
|||
val finalStyle = style ?: "none"
|
||||
val finalColor = color ?: spanStyle.color.takeIf { it.isSpecified } ?: Color.Black
|
||||
|
||||
val adaptedColor = this@CssParser.adaptColorForTheme(finalColor, isDarkTheme, isBackground = false)
|
||||
val adaptedColor = this@CssParser.adaptColorForTheme(finalColor, isDarkTheme, isBackground = false, themeBackgroundColor, themeTextColor)
|
||||
|
||||
if (finalWidth > 0.dp && finalStyle != "none" && finalStyle != "hidden") {
|
||||
return BorderStyle(finalWidth, adaptedColor, finalStyle)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue