App themes (#217)

* Updated `.gitattributes` to correctly vendor all files within the `libmobi` and `woff2` directories.

* Improved TTS chapter transition handling

* Improved TTS stability and chapter advancement in the EPUB reader.

* Implemented PDF metadata extraction for titles and authors using PdfiumCore in `MainViewModel` and `MetadataExtractionWorker`.

* Implemented support for interactive footnotes in the EPUB vertical reader.

* Added support for footnotes in the paginated EPUB reader.

* Implemented a customizable app theming system.

* Added support for app-wide contrast and text brightness customization in the "App Theme" option.

* option to adjust pull-to-chapter-change drag in the EPUB vertical reader under visual options.

* Updated `PdfViewerScreen` to replace the standalone full-screen toggle with a more comprehensive "Visual Options" system UI management tool.
This commit is contained in:
Aryan 2026-04-21 18:11:55 +05:30 committed by GitHub
parent 4cb25e6abd
commit 71e3614ad6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1413 additions and 287 deletions

View file

@ -498,6 +498,7 @@ fun EpubReaderHost(
var pendingNoteForNewHighlight by remember { mutableStateOf(false) }
var highlightToNoteCfi by remember { mutableStateOf<String?>(null) }
var activeFootnoteHtml by remember { mutableStateOf<String?>(null) }
var showJustifyWarningDialog by remember { mutableStateOf(false) }
var isNavigatingByToc by remember { mutableStateOf(false) }
@ -509,6 +510,7 @@ fun EpubReaderHost(
var systemUiMode by remember { mutableStateOf(loadSystemUiMode(context)) }
var pageInfoMode by remember { mutableStateOf(loadPageInfoMode(context)) }
var pullToTurnEnabled by remember { mutableStateOf(loadPullToTurn(context)) }
var pullToTurnMultiplier by remember { mutableFloatStateOf(loadPullToTurnMultiplier(context)) }
var showVisualOptionsSheet by remember { mutableStateOf(false) }
var removeEdgePadding by remember { mutableStateOf(loadRemoveEdgePadding(context)) }
@ -809,7 +811,6 @@ fun EpubReaderHost(
var ttsShouldStartOnChapterLoad by remember { mutableStateOf(false) }
var userStoppedTts by remember { mutableStateOf(false) }
var skipChapterRequest by remember { mutableStateOf(false) }
var ttsChapterIndex by remember { mutableStateOf<Int?>(null) }
var searchHighlightTarget by remember { mutableStateOf<SearchResult?>(null) }
@ -878,7 +879,7 @@ fun EpubReaderHost(
var activeFragmentId by remember { mutableStateOf<String?>(null) }
val density = LocalDensity.current
val dragThresholdPx = with(density) { DRAG_TO_CHANGE_CHAPTER_THRESHOLD_DP.toPx() }
val dragThresholdPx = with(density) { DRAG_TO_CHANGE_CHAPTER_THRESHOLD_DP.toPx() * pullToTurnMultiplier }
var currentScrollYPosition by rememberSaveable(epubBook.title) {
mutableIntStateOf(0)
@ -1000,20 +1001,6 @@ fun EpubReaderHost(
}
}
LaunchedEffect(skipChapterRequest) {
if (skipChapterRequest) {
skipChapterRequest = false
if (ttsShouldStartOnChapterLoad && currentChapterIndex < chapters.size - 1) {
Timber.d("Executing skip chapter request for continuous TTS.")
currentScrollYPosition = 0
currentScrollHeightValue = 0
currentChapterIndex++
} else {
ttsShouldStartOnChapterLoad = false
}
}
}
val searchState = rememberSearchState(scope = scope, searcher = epubSearcher)
val speakerPlayer = remember(context, scope) {
SpeakerSamplePlayer(context, scope, getAuthToken = { viewModel.getAuthToken() })
@ -1135,7 +1122,7 @@ fun EpubReaderHost(
}
fun startTts() {
if (currentTtsMode == com.aryan.reader.tts.TtsPlaybackManager.TtsMode.CLOUD && credits <= 0) {
if (currentTtsMode == TtsPlaybackManager.TtsMode.CLOUD && credits <= 0) {
showInsufficientCreditsDialog = true
return
}
@ -1193,7 +1180,7 @@ fun EpubReaderHost(
)
fun startTtsFromSelectionPaginated(baseCfi: String, startOffset: Int) {
if (currentTtsMode == com.aryan.reader.tts.TtsPlaybackManager.TtsMode.CLOUD && credits <= 0) {
if (currentTtsMode == TtsPlaybackManager.TtsMode.CLOUD && credits <= 0) {
showInsufficientCreditsDialog = true
return
}
@ -1275,17 +1262,23 @@ fun EpubReaderHost(
ttsChapterIndex = ttsChapterIndex,
onTtsChapterIndexChange = { newIndex -> ttsChapterIndex = newIndex },
onNavigateToChapter = { nextIndex ->
Timber.tag("TTS_CHAPTER_CHANGE_DIAG").d("TtsSessionObserver triggered onNavigateToChapter to: $nextIndex")
initialScrollTargetForChapter = ChapterScrollPosition.START
cfiToLoad = null
currentScrollYPosition = 0
currentScrollHeightValue = 0
currentChapterIndex = nextIndex
},
onToggleTtsStartOnLoad = { shouldStart -> ttsShouldStartOnChapterLoad = shouldStart },
onToggleTtsStartOnLoad = { shouldStart ->
Timber.tag("TTS_CHAPTER_CHANGE_DIAG").d("ttsShouldStartOnChapterLoad set to: $shouldStart")
ttsShouldStartOnChapterLoad = shouldStart
},
userStoppedTts = userStoppedTts,
scope = scope,
currentTtsMode = currentTtsMode,
getAuthToken = { viewModel.getAuthToken() }
getAuthToken = { viewModel.getAuthToken() },
locatorConverter = locatorConverter,
epubBook = epubBook
)
TtsHighlightHandler(
@ -2411,6 +2404,8 @@ fun EpubReaderHost(
CircularProgressIndicator()
}
} else if (chapterChunks.isNotEmpty()) {
var hasRequestedExtractionForThisChapter by remember(targetChapterIndex) { mutableStateOf(false) }
val initialContentToLoad = remember(loadUpToChunkIndex, chapterChunks) {
val targetIdx = loadUpToChunkIndex
val startIdx = 0
@ -2569,8 +2564,9 @@ fun EpubReaderHost(
Timber.d("Auto-save enabled immediately.")
}
if (ttsShouldStartOnChapterLoad) {
if (ttsShouldStartOnChapterLoad && !hasRequestedExtractionForThisChapter) {
Timber.d("Auto-starting TTS for new chapter ($targetChapterIndex).")
hasRequestedExtractionForThisChapter = true
scope.launch {
delay(200)
webViewRefForTts?.evaluateJavascript(
@ -2773,7 +2769,7 @@ fun EpubReaderHost(
onTtsTextReady = { jsonString ->
scope.launch {
val token = viewModel.getAuthToken()
Timber.tag("TTS_LIST_DIAG").d("Vertical: Processing received JSON. Length: ${jsonString.length}") // Add this
Timber.tag("TTS_LIST_DIAG").d("Vertical: Processing received JSON. Length: ${jsonString.length}")
val ttsChunks = mutableListOf<TtsChunk>()
try {
val jsonArray = JSONArray(jsonString)
@ -2808,18 +2804,21 @@ fun EpubReaderHost(
Timber.d("Vertical: Final compiled TTS chunks size: ${ttsChunks.size}")
if (ttsChunks.isNotEmpty()) {
if (currentTtsMode == com.aryan.reader.tts.TtsPlaybackManager.TtsMode.CLOUD && credits <= 0) {
if (currentTtsMode == TtsPlaybackManager.TtsMode.CLOUD && credits <= 0) {
showInsufficientCreditsDialog = true
ttsShouldStartOnChapterLoad = false
return@launch
}
ttsShouldStartOnChapterLoad = false
val chapterTitle = chapters.getOrNull(currentChapterIndex)?.title
userStoppedTts = false
val chapterTitle = chapters.getOrNull(targetChapterIndex)?.title
val coverUriString = coverImagePath?.let {
Uri.fromFile(File(it)).toString()
}
ttsChapterIndex = currentChapterIndex
ttsChapterIndex = targetChapterIndex
ttsController.start(
chunks = ttsChunks,
bookTitle = epubBook.title,
@ -2830,12 +2829,16 @@ fun EpubReaderHost(
authToken = token
)
} else {
Timber.w("No TTS chunks were created from JSON, not starting TTS."
)
Timber.w("No TTS chunks were created from JSON, not starting TTS.")
if (ttsShouldStartOnChapterLoad) {
Timber.d("Empty chapter detected during continuous TTS. Requesting skip."
)
skipChapterRequest = true
Timber.d("Empty chapter detected during start. Advancing UI to next chapter.")
val nextIdx = targetChapterIndex + 1
if (nextIdx < chapters.size) {
initialScrollTargetForChapter = ChapterScrollPosition.START
currentScrollYPosition = 0
currentScrollHeightValue = 0
currentChapterIndex = nextIdx
}
}
}
}
@ -2899,6 +2902,9 @@ fun EpubReaderHost(
)
}
},
onFootnoteRequested = { html ->
activeFootnoteHtml = html
},
isProUser = isProUser,
isOss = BuildConfig.FLAVOR == "oss",
onShowDictionaryUpsellDialog = {
@ -3241,6 +3247,9 @@ fun EpubReaderHost(
pendingNoteForNewHighlight = true
}
},
onFootnoteRequested = { html ->
activeFootnoteHtml = html
},
onHighlightDeleted = { cfi ->
val toRemove = userHighlights.find { it.cfi == cfi }
if (toRemove != null) {
@ -4317,6 +4326,15 @@ fun EpubReaderHost(
}
}
if (activeFootnoteHtml != null) {
FootnoteBottomSheet(
htmlContent = activeFootnoteHtml!!,
effectiveBg = effectiveBg,
effectiveText = effectiveText,
onDismiss = { activeFootnoteHtml = null }
)
}
CustomTopBanner(bannerMessage = bannerMessage)
}
}
@ -4452,6 +4470,11 @@ fun EpubReaderHost(
removeEdgePadding = it
saveRemoveEdgePadding(context, it)
},
pullToTurnMultiplier = pullToTurnMultiplier,
onPullToTurnMultiplierChange = {
pullToTurnMultiplier = it
savePullToTurnMultiplier(context, it)
},
onDismiss = { showVisualOptionsSheet = false }
)
}