Added customizable highlight color palette in epub (#6)

This introduces a user-configurable palette for text highlighting. The four default highlight colors can now be customized by the user from an expanded set of predefined colors.

Changes include:
- Added a `PaletteManagerDialog` to allow users to select and save their preferred highlight colors.
- Implemented `saveHighlightPalette` and `loadHighlightPalette` to persist the user's choices.
- The highlighting UI in both vertical and paginated readers now displays the active custom palette.
- Added new `HighlightColor` options.
- Updated JavaScript to support the expanded color set and improve highlight interaction logic.
This commit is contained in:
Aryan 2026-02-26 14:26:54 +05:30 committed by GitHub
parent 1b048c9073
commit 1f111034f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 3044 additions and 1753 deletions

View file

@ -17,6 +17,7 @@
*
* mail: epistemereader@gmail.com
*/
// ChapterWebView.kt
package com.aryan.reader.epubreader
import android.annotation.SuppressLint
@ -27,7 +28,6 @@ import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Rect
import timber.log.Timber
import android.webkit.JavascriptInterface
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
@ -36,13 +36,14 @@ import android.webkit.WebViewClient
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -69,6 +70,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
@ -86,6 +88,7 @@ import com.aryan.reader.countWords
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.json.JSONObject
import timber.log.Timber
import java.io.BufferedReader
import java.io.InputStreamReader
@ -326,6 +329,8 @@ fun ChapterWebView(
currentTextAlign: ReaderTextAlign,
onHighlightClicked: () -> Unit,
onAutoScrollChapterEnd: () -> Unit = {},
activeHighlightPalette: List<HighlightColor>,
onUpdatePalette: (Int, HighlightColor) -> Unit
) {
Timber.d(
"RenderChapterViaWebView for '$chapterTitle', Key: $key, isDarkTheme: $isDarkTheme, initialScrollTarget: $initialScrollTarget"
@ -340,6 +345,8 @@ fun ChapterWebView(
val jsToInject = remember(context) { getJsToInject(context) }
var showPaletteManager by remember { mutableStateOf(false) }
LaunchedEffect(currentFontSize, currentLineHeight) {
localWebViewRef?.evaluateJavascript(
"javascript:if(window.getSelection) window.getSelection().removeAllRanges();",
@ -542,9 +549,9 @@ fun ChapterWebView(
}
addJavascriptInterface(
CfiJsBridge(
onCfiReady = { cfi -> onCfiGenerated(cfi) },
onCfiForBookmarkReady = { cfi -> onBookmarkCfiGenerated(cfi) }
), "CfiBridge")
onCfiReady = { cfi -> onCfiGenerated(cfi) },
onCfiForBookmarkReady = { cfi -> onBookmarkCfiGenerated(cfi) }
), "CfiBridge")
addJavascriptInterface(SnippetJsBridge { cfi, snippet ->
onSnippetForBookmarkReady(
cfi,
@ -812,45 +819,51 @@ fun ChapterWebView(
Column(
modifier = Modifier.width(IntrinsicSize.Max)
) {
// 1. Color Row (Improved sizing and gaps)
Row(
modifier = Modifier
.padding(vertical = 12.dp, horizontal = 12.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center, // Centered colors
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
HighlightColor.entries.forEach { colorEnum ->
activeHighlightPalette.forEachIndexed { index, colorEnum ->
Box(
modifier = Modifier
.padding(horizontal = 8.dp)
.size(24.dp)
.padding(horizontal = 6.dp)
.size(32.dp)
.background(colorEnum.color, CircleShape)
.border(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.5f), CircleShape)
.clickable {
Timber.d("Kotlin: Color clicked. Existing? ${state.isExistingHighlight}")
if (state.isExistingHighlight && state.cfi != null) {
// UPDATE EXISTING HIGHLIGHT
Timber.d("Kotlin: Requesting UPDATE via JS for CFI: ${state.cfi}")
localWebViewRef?.evaluateJavascript(
"javascript:window.HighlightBridgeHelper.updateHighlightStyle('${state.cfi}', '${colorEnum.cssClass}', '${colorEnum.id}');",
null
)
} else {
// CREATE NEW HIGHLIGHT
Timber.d("Kotlin: Requesting CREATE via JS")
localWebViewRef?.evaluateJavascript(
"javascript:window.HighlightBridgeHelper.createUserHighlight('${colorEnum.cssClass}', '${colorEnum.id}');",
null
)
}
state.finishActionModeCallback()
localWebViewRef?.clearFocus()
customMenuState = null
.pointerInput(colorEnum) {
detectTapGestures(
onTap = {
Timber.d("Kotlin: Color clicked. Existing? ${state.isExistingHighlight}")
if (state.isExistingHighlight && state.cfi != null) {
localWebViewRef?.evaluateJavascript(
"javascript:window.HighlightBridgeHelper.updateHighlightStyle('${state.cfi}', '${colorEnum.cssClass}', '${colorEnum.id}');",
null
)
} else {
localWebViewRef?.evaluateJavascript(
"javascript:window.HighlightBridgeHelper.createUserHighlight('${colorEnum.cssClass}', '${colorEnum.id}');",
null
)
}
state.finishActionModeCallback()
localWebViewRef?.clearFocus()
customMenuState = null
},
onLongPress = {
showPaletteManager = true
}
)
}
)
}
Spacer(modifier = Modifier.width(8.dp))
SpectrumButton(
onClick = { showPaletteManager = true },
size = 32.dp
)
}
// 2. Delete Option (Only for existing highlights)
@ -979,5 +992,17 @@ fun ChapterWebView(
}
}
}
if (showPaletteManager) {
PaletteManagerDialog(
currentPalette = activeHighlightPalette,
onDismiss = { showPaletteManager = false },
onSave = { newPalette ->
newPalette.forEachIndexed { index, color ->
onUpdatePalette(index, color)
}
showPaletteManager = false
}
)
}
}
}