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
|
|
@ -107,6 +107,8 @@ class BookPaginator(
|
|||
private val density: Density,
|
||||
private val fontFamilyMap: Map<String, FontFamily>,
|
||||
private val isDarkTheme: Boolean,
|
||||
private val themeBackgroundColor: Color,
|
||||
private val themeTextColor: Color,
|
||||
private val bookId: String,
|
||||
private val initialChapterToPaginate: Int,
|
||||
private val bookCss: Map<String, String>,
|
||||
|
|
@ -127,7 +129,7 @@ class BookPaginator(
|
|||
override var generation by mutableIntStateOf(0)
|
||||
private set
|
||||
|
||||
override val pageShiftRequest = MutableSharedFlow<Int>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
override val pageShiftRequest = MutableSharedFlow<Int>(extraBufferCapacity = 100, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
private val currentUserChapterIndex = MutableStateFlow(initialChapterToPaginate)
|
||||
|
||||
internal val chapterPageCounts = ConcurrentHashMap<Int, Int>()
|
||||
|
|
@ -404,6 +406,8 @@ class BookPaginator(
|
|||
fontFamilyMap = fontFamilyMap,
|
||||
density = density,
|
||||
isDarkTheme = isDarkTheme,
|
||||
themeBackgroundColor = themeBackgroundColor,
|
||||
themeTextColor = themeTextColor,
|
||||
chapterAbsPath = chapter.absPath,
|
||||
extractionBasePath = extractionBasePath,
|
||||
userTextAlign = userTextAlign
|
||||
|
|
@ -475,10 +479,10 @@ class BookPaginator(
|
|||
val processedHtml = document.outerHtml()
|
||||
|
||||
var parsingCssRules = OptimizedCssRules()
|
||||
val uaResult = CssParser.parse(cssContent = userAgentStylesheet, cssPath = null, baseFontSizeSp = textStyle.fontSize.value, density = density.density, constraints = constraints, isDarkTheme = false)
|
||||
val uaResult = CssParser.parse(cssContent = userAgentStylesheet, cssPath = null, baseFontSizeSp = textStyle.fontSize.value, density = density.density, constraints = constraints, isDarkTheme = false, themeBackgroundColor = themeBackgroundColor, themeTextColor = themeTextColor)
|
||||
parsingCssRules = parsingCssRules.merge(uaResult.rules)
|
||||
bookCss.forEach { (path, content) ->
|
||||
val bookCssResult = CssParser.parse(cssContent = content, cssPath = path, baseFontSizeSp = textStyle.fontSize.value, density = density.density, constraints = constraints, isDarkTheme = false)
|
||||
val bookCssResult = CssParser.parse(cssContent = content, cssPath = path, baseFontSizeSp = textStyle.fontSize.value, density = density.density, constraints = constraints, isDarkTheme = false, themeBackgroundColor = themeBackgroundColor, themeTextColor = themeTextColor)
|
||||
parsingCssRules = parsingCssRules.merge(bookCssResult.rules)
|
||||
}
|
||||
|
||||
|
|
@ -950,8 +954,8 @@ class BookPaginator(
|
|||
return@launch
|
||||
}
|
||||
|
||||
val chapterStartPage = calculateAccurateStartIndex(targetChapterIndex)
|
||||
val chapterPages = pageCache[targetChapterIndex] ?: paginateChapter(targetChapterIndex)
|
||||
val chapterStartPage = calculateAccurateStartIndex(targetChapterIndex)
|
||||
|
||||
if (chapterPages == null) {
|
||||
Timber.e("Href Navigation failed: Could not paginate target chapter $targetChapterIndex.")
|
||||
|
|
@ -983,8 +987,8 @@ class BookPaginator(
|
|||
val targetChapterIndex = result.locationInSource
|
||||
Timber.i("Finding page for search result: '${result.query}' in chapter $targetChapterIndex")
|
||||
|
||||
val chapterStartPage = calculateAccurateStartIndex(targetChapterIndex)
|
||||
val chapterPages = pageCache[targetChapterIndex] ?: paginateChapter(targetChapterIndex)
|
||||
val chapterStartPage = calculateAccurateStartIndex(targetChapterIndex)
|
||||
|
||||
if (chapterPages == null) {
|
||||
Timber.e("Search result navigation failed: Could not paginate target chapter $targetChapterIndex.")
|
||||
|
|
@ -1055,8 +1059,8 @@ class BookPaginator(
|
|||
val targetChapterIndex = locator.chapterIndex
|
||||
Timber.i("Finding page for locator: Chapter $targetChapterIndex, Block ${locator.blockIndex}, Offset ${locator.charOffset}")
|
||||
|
||||
val chapterStartPage = chapterStartPageIndices[targetChapterIndex] ?: 0
|
||||
val chapterPages = pageCache[targetChapterIndex] ?: paginateChapter(targetChapterIndex)
|
||||
val chapterStartPage = chapterStartPageIndices[targetChapterIndex] ?: 0
|
||||
|
||||
if (chapterPages.isNullOrEmpty()) {
|
||||
Timber.e("Locator navigation failed: Could not paginate target chapter $targetChapterIndex.")
|
||||
|
|
@ -1068,6 +1072,8 @@ class BookPaginator(
|
|||
for ((pageIndex, page) in chapterPages.withIndex()) {
|
||||
for (block in page.content) {
|
||||
if (block.blockIndex == locator.blockIndex) {
|
||||
Timber.tag("ThemeReconfig").d("Block Index Match: Found block ${locator.blockIndex} on page $pageIndex of Chapter $targetChapterIndex")
|
||||
|
||||
if (fallbackPageInChapter == -1) {
|
||||
fallbackPageInChapter = pageIndex
|
||||
}
|
||||
|
|
@ -1077,19 +1083,20 @@ class BookPaginator(
|
|||
val startOffsetOnPage = textBlock.startCharOffsetInSource
|
||||
val endOffsetOnPage = startOffsetOnPage + textBlock.content.length
|
||||
|
||||
if (locator.charOffset in startOffsetOnPage..<endOffsetOnPage) {
|
||||
val isInside = locator.charOffset in startOffsetOnPage..<endOffsetOnPage
|
||||
Timber.tag("ThemeReconfig").d("Offset Check: Target ${locator.charOffset} vs Range [$startOffsetOnPage, $endOffsetOnPage]. Inside: $isInside")
|
||||
|
||||
if (isInside) {
|
||||
val finalPageIndex = chapterStartPage + pageIndex
|
||||
Timber.i("Locator navigation SUCCEEDED with offset. Final page index: $finalPageIndex")
|
||||
return finalPageIndex
|
||||
}
|
||||
} else {
|
||||
val finalPageIndex = chapterStartPage + pageIndex
|
||||
Timber.i("Locator navigation SUCCEEDED for non-text block. Final page index: $finalPageIndex")
|
||||
return finalPageIndex
|
||||
return chapterStartPage + pageIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Timber.tag("ThemeReconfig").e("Block Index NOT FOUND: Could not find block ${locator.blockIndex} in any page of Chapter $targetChapterIndex")
|
||||
|
||||
if (fallbackPageInChapter != -1) {
|
||||
val finalPageIndex = chapterStartPage + fallbackPageInChapter
|
||||
|
|
@ -1120,9 +1127,9 @@ class BookPaginator(
|
|||
coroutineScope.launch(Dispatchers.IO) {
|
||||
Timber.i("findPageForCfi: Starting search for CFI: '$cfi' in chapter: '$chapterIndex'")
|
||||
|
||||
val chapterPages = pageCache[chapterIndex] ?: paginateChapter(chapterIndex)
|
||||
val chapterStartPage = calculateAccurateStartIndex(chapterIndex)
|
||||
Timber.d("findPageForCfi: Chapter $chapterIndex starts at absolute page $chapterStartPage.")
|
||||
val chapterPages = pageCache[chapterIndex] ?: paginateChapter(chapterIndex)
|
||||
|
||||
if (chapterPages == null) {
|
||||
Timber.e("CFI Navigation failed: Could not paginate target chapter $chapterIndex.")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ import androidx.compose.ui.platform.LocalTextToolbar
|
|||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbarStatus
|
||||
import androidx.compose.ui.res.imageResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.PlatformTextStyle
|
||||
|
|
@ -490,6 +491,8 @@ private fun WrappingContentLayout(
|
|||
fun PaginatedReaderScreen(
|
||||
book: EpubBook,
|
||||
isDarkTheme: Boolean,
|
||||
effectiveBg: Color,
|
||||
effectiveText: Color,
|
||||
pagerState: PagerState,
|
||||
isPageTurnAnimationEnabled: Boolean,
|
||||
searchQuery: String,
|
||||
|
|
@ -513,7 +516,8 @@ fun PaginatedReaderScreen(
|
|||
onHighlightCreated: (String, String, String) -> Unit,
|
||||
onHighlightDeleted: (String) -> Unit,
|
||||
activeHighlightPalette: List<HighlightColor>,
|
||||
onUpdatePalette: (Int, HighlightColor) -> Unit
|
||||
onUpdatePalette: (Int, HighlightColor) -> Unit,
|
||||
activeTextureId: String? = null
|
||||
) {
|
||||
LaunchedEffect(userHighlights) {
|
||||
Timber.d("PaginatedReaderScreen: Received ${userHighlights.size} highlights.")
|
||||
|
|
@ -522,10 +526,26 @@ fun PaginatedReaderScreen(
|
|||
}
|
||||
}
|
||||
|
||||
BoxWithConstraints(modifier = modifier.fillMaxSize()) {
|
||||
val context = LocalContext.current
|
||||
val textureBitmap = remember(activeTextureId) {
|
||||
activeTextureId?.let { id ->
|
||||
com.aryan.reader.epubreader.ReaderTexture.entries.find { it.id == id }?.resId?.let { resId ->
|
||||
androidx.compose.ui.graphics.ImageBitmap.imageResource(context.resources, resId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val textureModifier = if (textureBitmap != null) {
|
||||
Modifier.drawBehind {
|
||||
val brush = androidx.compose.ui.graphics.ShaderBrush(
|
||||
androidx.compose.ui.graphics.ImageShader(textureBitmap, androidx.compose.ui.graphics.TileMode.Repeated, androidx.compose.ui.graphics.TileMode.Repeated)
|
||||
)
|
||||
drawRect(brush = brush, blendMode = BlendMode.Multiply, alpha = 0.6f)
|
||||
}
|
||||
} else Modifier
|
||||
|
||||
BoxWithConstraints(modifier = modifier.fillMaxSize().background(effectiveBg).then(textureModifier)) {
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val textColor = if (isDarkTheme) MaterialTheme.colorScheme.onBackground
|
||||
else MaterialTheme.colorScheme.onSurface
|
||||
val baseTextStyle = MaterialTheme.typography.bodyLarge
|
||||
|
||||
var debouncedFontSizeMult by remember { mutableFloatStateOf(fontSizeMultiplier) }
|
||||
|
|
@ -536,22 +556,36 @@ fun PaginatedReaderScreen(
|
|||
var anchorLocatorForReconfig by remember { mutableStateOf<Locator?>(null) }
|
||||
val currentPaginatorRef = remember { mutableStateOf<IPaginator?>(null) }
|
||||
|
||||
val previousConstraints = remember { arrayOf(this.constraints) }
|
||||
if (previousConstraints[0] != this.constraints) {
|
||||
val previousState = remember {
|
||||
arrayOf<Any>(this.constraints, isDarkTheme, effectiveBg, effectiveText)
|
||||
}
|
||||
|
||||
if (previousState[0] != this.constraints ||
|
||||
previousState[1] != isDarkTheme ||
|
||||
previousState[2] != effectiveBg ||
|
||||
previousState[3] != effectiveText
|
||||
) {
|
||||
val activePaginator = currentPaginatorRef.value
|
||||
if (activePaginator is BookPaginator) {
|
||||
val currentPage = pagerState.currentPage
|
||||
val locator = activePaginator.getLocatorForPage(currentPage)
|
||||
if (locator != null) {
|
||||
anchorLocatorForReconfig = locator
|
||||
}
|
||||
anchorLocatorForReconfig = locator
|
||||
|
||||
Timber.tag("ThemeReconfig").d("""
|
||||
RECONFIG DETECTED
|
||||
- Reason: ${if (previousState[0] != this.constraints) "Constraints" else "Theme/Colors"}
|
||||
- Current Page: $currentPage
|
||||
- Saved Locator: $locator
|
||||
""".trimIndent())
|
||||
}
|
||||
previousConstraints[0] = this.constraints
|
||||
previousState[0] = this.constraints
|
||||
previousState[1] = isDarkTheme
|
||||
previousState[2] = effectiveBg
|
||||
previousState[3] = effectiveText
|
||||
}
|
||||
|
||||
val textStyle = remember(
|
||||
baseTextStyle,
|
||||
textColor,
|
||||
baseTextStyle, effectiveText,
|
||||
debouncedFontSizeMult,
|
||||
debouncedLineHeightMult,
|
||||
debouncedFontFamily
|
||||
|
|
@ -560,7 +594,7 @@ fun PaginatedReaderScreen(
|
|||
val adjustedLineHeight = adjustedFontSize * debouncedLineHeightMult
|
||||
|
||||
baseTextStyle.copy(
|
||||
color = textColor,
|
||||
color = effectiveText,
|
||||
fontSize = adjustedFontSize,
|
||||
lineHeight = adjustedLineHeight,
|
||||
fontFamily = debouncedFontFamily,
|
||||
|
|
@ -636,7 +670,7 @@ fun PaginatedReaderScreen(
|
|||
remember(initialChapterIndexInBook, anchorLocatorForReconfig) {
|
||||
anchorLocatorForReconfig?.chapterIndex ?: initialChapterIndexInBook ?: 0
|
||||
}
|
||||
val paginator = remember(book, textConstraints, isDarkTheme, textStyle, userTextAlign) {
|
||||
val paginator = remember(book, textConstraints, isDarkTheme, textStyle, userTextAlign, effectiveBg, effectiveText) {
|
||||
val userAgentStylesheet = UserAgentStylesheet.default
|
||||
var allRules = OptimizedCssRules()
|
||||
val allFontFaces = mutableListOf<FontFaceInfo>()
|
||||
|
|
@ -647,7 +681,9 @@ fun PaginatedReaderScreen(
|
|||
baseFontSizeSp = textStyle.fontSize.value,
|
||||
density = density.density,
|
||||
constraints = textConstraints,
|
||||
isDarkTheme = isDarkTheme
|
||||
isDarkTheme = isDarkTheme,
|
||||
themeBackgroundColor = effectiveBg,
|
||||
themeTextColor = effectiveText
|
||||
)
|
||||
allRules = allRules.merge(uaResult.rules)
|
||||
allFontFaces.addAll(uaResult.fontFaces)
|
||||
|
|
@ -659,7 +695,9 @@ fun PaginatedReaderScreen(
|
|||
baseFontSizeSp = textStyle.fontSize.value,
|
||||
density = density.density,
|
||||
constraints = textConstraints,
|
||||
isDarkTheme = isDarkTheme
|
||||
isDarkTheme = isDarkTheme,
|
||||
themeBackgroundColor = effectiveBg,
|
||||
themeTextColor = effectiveText
|
||||
)
|
||||
allRules = allRules.merge(bookCssResult.rules)
|
||||
allFontFaces.addAll(bookCssResult.fontFaces)
|
||||
|
|
@ -687,6 +725,8 @@ fun PaginatedReaderScreen(
|
|||
density = density,
|
||||
fontFamilyMap = fontFamilyMap,
|
||||
isDarkTheme = isDarkTheme,
|
||||
themeBackgroundColor = effectiveBg,
|
||||
themeTextColor = effectiveText,
|
||||
bookId = uniqueBookId,
|
||||
bookCacheDao = bookCacheDao,
|
||||
proto = proto,
|
||||
|
|
@ -707,31 +747,28 @@ fun PaginatedReaderScreen(
|
|||
|
||||
LaunchedEffect(paginator) {
|
||||
if (anchorLocatorForReconfig != null) {
|
||||
Timber.d("Waiting for paginator to initialize before restoring anchor...")
|
||||
Timber.tag("ThemeReconfig").d("Restoration Effect Triggered for Locator: $anchorLocatorForReconfig")
|
||||
|
||||
// Suspend until isLoading becomes false
|
||||
snapshotFlow { paginator.isLoading }.filter { !it }.first()
|
||||
|
||||
val targetLocator = anchorLocatorForReconfig
|
||||
if (targetLocator != null) {
|
||||
Timber.d(
|
||||
"Paginator initialized. Restoring anchor: Chapter=${targetLocator.chapterIndex}, Block=${targetLocator.blockIndex}, Offset=${targetLocator.charOffset}"
|
||||
)
|
||||
|
||||
val page = paginator.findPageForLocator(targetLocator)
|
||||
|
||||
Timber.tag("ThemeReconfig").d("""
|
||||
Restoration Progress:
|
||||
- Target Locator: $targetLocator
|
||||
- Paginator found Page: $page
|
||||
- Chapter Start Page: ${paginator.chapterStartPageIndices[targetLocator.chapterIndex]}
|
||||
""".trimIndent())
|
||||
|
||||
if (page != null) {
|
||||
pagerState.scrollToPage(page)
|
||||
Timber.d("Restored to page: $page")
|
||||
} else {
|
||||
val startPage =
|
||||
paginator.chapterStartPageIndices[targetLocator.chapterIndex]
|
||||
val startPage = paginator.chapterStartPageIndices[targetLocator.chapterIndex]
|
||||
if (startPage != null) {
|
||||
Timber.w(
|
||||
"Exact locator not found. Falling back to start of chapter at page $startPage"
|
||||
)
|
||||
Timber.tag("ThemeReconfig").w("Precise page not found, falling back to chapter start: $startPage")
|
||||
pagerState.scrollToPage(startPage)
|
||||
} else {
|
||||
Timber.e("Failed to restore position. Chapter start index not found.")
|
||||
}
|
||||
}
|
||||
anchorLocatorForReconfig = null
|
||||
|
|
@ -780,6 +817,7 @@ fun PaginatedReaderScreen(
|
|||
uiState = uiState,
|
||||
pagerState = pagerState,
|
||||
isPageTurnAnimationEnabled = isPageTurnAnimationEnabled,
|
||||
effectiveBg = effectiveBg,
|
||||
searchQuery = searchQuery,
|
||||
ttsHighlightInfo = ttsHighlightInfo,
|
||||
textStyle = textStyle,
|
||||
|
|
@ -814,7 +852,8 @@ fun PaginatedReaderScreen(
|
|||
onHighlightDeleted = onHighlightDeleted,
|
||||
isDarkTheme = isDarkTheme,
|
||||
activeHighlightPalette = activeHighlightPalette,
|
||||
onUpdatePalette = onUpdatePalette
|
||||
onUpdatePalette = onUpdatePalette,
|
||||
effectiveText = effectiveText
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1366,6 +1405,8 @@ internal fun PaginatedReaderContent(
|
|||
uiState: PaginatedReaderUiState,
|
||||
pagerState: PagerState,
|
||||
isPageTurnAnimationEnabled: Boolean,
|
||||
effectiveBg: Color,
|
||||
effectiveText: Color,
|
||||
searchQuery: String,
|
||||
ttsHighlightInfo: TtsHighlightInfo?,
|
||||
textStyle: TextStyle,
|
||||
|
|
@ -1526,7 +1567,7 @@ internal fun PaginatedReaderContent(
|
|||
val pageModifier = if (isPageTurnAnimationEnabled) {
|
||||
Modifier
|
||||
.zIndex(zIndex)
|
||||
.realisticBookPage(pagerState, pageIndex, isDarkTheme, pageTurnTouchY) // UPDATED
|
||||
.realisticBookPage(pagerState, pageIndex, effectiveBg, isDarkTheme, pageTurnTouchY)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
|
@ -3396,6 +3437,7 @@ private fun RenderFlexChildBlock(
|
|||
private fun Modifier.realisticBookPage(
|
||||
pagerState: PagerState,
|
||||
pageIndex: Int,
|
||||
paperColor: Color,
|
||||
isDarkTheme: Boolean,
|
||||
touchY: Float?
|
||||
): Modifier = composed {
|
||||
|
|
@ -3419,7 +3461,6 @@ private fun Modifier.realisticBookPage(
|
|||
}
|
||||
.drawWithContent {
|
||||
val pageOffset = (pageIndex - pagerState.currentPage) - pagerState.currentPageOffsetFraction
|
||||
val paperColor = if (isDarkTheme) Color(0xFF121212) else Color(0xFFFFFFFF)
|
||||
|
||||
if (abs(pageOffset) < 0.001f) {
|
||||
drawRect(color = paperColor)
|
||||
|
|
@ -3507,8 +3548,10 @@ private fun Modifier.realisticBookPage(
|
|||
|
||||
clipRect(0f, 0f, w, h) {
|
||||
clipPath(frontPath) {
|
||||
val flapColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFF0F0F0)
|
||||
drawPath(reflectedScreenPath, color = flapColor)
|
||||
drawPath(reflectedScreenPath, color = paperColor)
|
||||
|
||||
val flapTint = if (isDarkTheme) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f)
|
||||
drawPath(reflectedScreenPath, color = flapTint)
|
||||
|
||||
val innerShadowWidth = shadowWidth * 0.7f
|
||||
val innerShadowBrush = Brush.linearGradient(
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ class PaginatedReaderViewModel : ViewModel() {
|
|||
textStyle: TextStyle,
|
||||
density: Density,
|
||||
isDarkTheme: Boolean,
|
||||
themeBackgroundColor: androidx.compose.ui.graphics.Color,
|
||||
themeTextColor: androidx.compose.ui.graphics.Color,
|
||||
context: Context,
|
||||
initialChapterToPaginate: Int?,
|
||||
mathMLRenderer: MathMLRenderer
|
||||
|
|
@ -83,7 +85,7 @@ class PaginatedReaderViewModel : ViewModel() {
|
|||
|
||||
// CSS Parsing and Font Loading
|
||||
val userAgentStylesheet = UserAgentStylesheet.default
|
||||
var allRules = OptimizedCssRules() // CHANGED from: mutableListOf<CssRule>()
|
||||
var allRules = OptimizedCssRules()
|
||||
val allFontFaces = mutableListOf<FontFaceInfo>()
|
||||
|
||||
val uaResult = CssParser.parse(
|
||||
|
|
@ -92,9 +94,11 @@ class PaginatedReaderViewModel : ViewModel() {
|
|||
baseFontSizeSp = textStyle.fontSize.value,
|
||||
density = density.density,
|
||||
constraints = textConstraints,
|
||||
isDarkTheme = isDarkTheme
|
||||
isDarkTheme = isDarkTheme,
|
||||
themeBackgroundColor = themeBackgroundColor,
|
||||
themeTextColor = themeTextColor
|
||||
)
|
||||
allRules = allRules.merge(uaResult.rules) // CHANGED
|
||||
allRules = allRules.merge(uaResult.rules)
|
||||
allFontFaces.addAll(uaResult.fontFaces)
|
||||
|
||||
book.css.forEach { (path, content) ->
|
||||
|
|
@ -104,9 +108,11 @@ class PaginatedReaderViewModel : ViewModel() {
|
|||
baseFontSizeSp = textStyle.fontSize.value,
|
||||
density = density.density,
|
||||
constraints = textConstraints,
|
||||
isDarkTheme = isDarkTheme
|
||||
isDarkTheme = isDarkTheme,
|
||||
themeBackgroundColor = themeBackgroundColor,
|
||||
themeTextColor = themeTextColor
|
||||
)
|
||||
allRules = allRules.merge(bookCssResult.rules) // CHANGED
|
||||
allRules = allRules.merge(bookCssResult.rules)
|
||||
allFontFaces.addAll(bookCssResult.fontFaces)
|
||||
}
|
||||
val fontFamilyMap = loadFontFamilies(
|
||||
|
|
@ -125,6 +131,8 @@ class PaginatedReaderViewModel : ViewModel() {
|
|||
density = density,
|
||||
fontFamilyMap = fontFamilyMap,
|
||||
isDarkTheme = isDarkTheme,
|
||||
themeBackgroundColor = themeBackgroundColor,
|
||||
themeTextColor = themeTextColor,
|
||||
bookId = bookId,
|
||||
bookCacheDao = bookCacheDao,
|
||||
proto = proto,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue