Bug and crash fixes (#132)
* feat(EPUB): adapt status bar icon visibility to reader theme
Pass the reader's active isDarkTheme state to EpubReaderSystemUiController to ensure status bar icons automatically switch between light and dark to remain visible against the selected background/page color.
* Implement storage tracking and optimize cache management
This commit introduces a `StorageTracker` to monitor disk usage and implements several optimizations to prevent cache bloat and handle orphaned files.
* refactor: unify book extraction paths and fix cache leaks
- Replaced random UUID and title-based hashing with stable `bookId` for extraction directories across Epub, Mobi, and FB2 parsers.
- Standardized all temporary extraction paths to `cache/imported_file_${bookId}`.
- Updated MainViewModel and MetadataExtractionWorker to provide the mandatory `bookId` during parsing.
- Ensured `clearImportedFileCache` successfully targets the correct directories on book deletion.
- Added a legacy cleanup task in `sweepOrphanedCache` to reclaim storage from old `extracted_epubs` folders.
* fix(pdf): handle NoClassDefFoundError for PdfPasswordException
Updated the PDF loading logic in PdfViewerScreen to catch Throwable
instead of Exception. This prevents the app from crashing when the
underlying pdfium library attempts to throw a PdfPasswordException
that is missing from the runtime classpath (NoClassDefFoundError).
* fix(tts): set language before voice to prevent variant reset
* EpubReaderScreen: increase scroll-hide ignore duration for tap toggles
Increase the threshold for ignoring tap toggles after bars are hidden by scrolling from 250ms to 400ms. This prevents accidental UI toggles caused by "sloppy taps" immediately following a scroll action.
* fix: resolve large bitmap crash on tall PDF pages
- Caps base layer bitmap dimensions to 3000px to prevent GPU texture limit crashes.
- Enables tiled rendering for large pages even at 1x zoom to maintain sharpness.
- Optimizes PdfBitmapPool to support rectangular bitmap allocations.
* Common.kt: ensure unique keys in voice list
* fix: handle ActivityNotFoundException in folder sync screen
* Improve file deletion logic and fix chapter index bounds in epub reader
* fix: ensure WebView bridge callbacks run on UI thread
This resolves a WebViewMethodCalledOnWrongThreadViolation in vertical mode that was preventing chapter chunks from loading beyond the initial viewport.
This commit is contained in:
parent
0d37bcefc3
commit
355664fbcc
16 changed files with 502 additions and 280 deletions
|
|
@ -490,50 +490,61 @@ fun ChapterWebView(
|
|||
localWebViewRef = this
|
||||
onWebViewInstanceCreated(this)
|
||||
addJavascriptInterface(
|
||||
PageInfoBridge(onScrollStateUpdate), "PageInfoReporter"
|
||||
PageInfoBridge { scrollY, scrollHeight, clientHeight, activeFragmentId ->
|
||||
this.post { onScrollStateUpdate(scrollY, scrollHeight, clientHeight, activeFragmentId) }
|
||||
}, "PageInfoReporter"
|
||||
)
|
||||
addJavascriptInterface(
|
||||
ProgressJsBridge(onTopChunkUpdated), "ProgressReporter"
|
||||
ProgressJsBridge { chunkIndex ->
|
||||
this.post { onTopChunkUpdated(chunkIndex) }
|
||||
}, "ProgressReporter"
|
||||
)
|
||||
addJavascriptInterface(ContentBridge(onChunkRequested), "ContentBridge")
|
||||
addJavascriptInterface(ContentBridge { index ->
|
||||
this.post { onChunkRequested(index) }
|
||||
}, "ContentBridge")
|
||||
|
||||
addJavascriptInterface(
|
||||
HighlightJsBridge(
|
||||
onCreateCallback = onHighlightCreated,
|
||||
onClickCallback = { cfi, text, left, top, right, bottom ->
|
||||
onCreateCallback = { cfi, text, colorId ->
|
||||
this.post { onHighlightCreated(cfi, text, colorId) }
|
||||
},
|
||||
onClickCallback = { cfi, text, left, top, right, bottom ->
|
||||
this.post {
|
||||
onHighlightClicked()
|
||||
|
||||
onHighlightClicked()
|
||||
val densityValue = density.density
|
||||
val locationOnScreen = IntArray(2)
|
||||
this.getLocationOnScreen(locationOnScreen)
|
||||
val xOffset = locationOnScreen[0]
|
||||
val yOffset = locationOnScreen[1]
|
||||
|
||||
val densityValue = density.density
|
||||
val locationOnScreen = IntArray(2)
|
||||
this.getLocationOnScreen(locationOnScreen)
|
||||
val xOffset = locationOnScreen[0]
|
||||
val yOffset = locationOnScreen[1]
|
||||
|
||||
val rect = Rect(
|
||||
(left * densityValue).toInt() + xOffset,
|
||||
(top * densityValue).toInt() + yOffset,
|
||||
(right * densityValue).toInt() + xOffset,
|
||||
(bottom * densityValue).toInt() + yOffset
|
||||
)
|
||||
|
||||
customMenuState = CustomMenuState(
|
||||
selectedText = text,
|
||||
selectionBounds = rect,
|
||||
finishActionModeCallback = {
|
||||
localWebViewRef?.evaluateJavascript(
|
||||
"javascript:if(window.getSelection) window.getSelection().removeAllRanges();",
|
||||
null
|
||||
val rect = Rect(
|
||||
(left * densityValue).toInt() + xOffset,
|
||||
(top * densityValue).toInt() + yOffset,
|
||||
(right * densityValue).toInt() + xOffset,
|
||||
(bottom * densityValue).toInt() + yOffset
|
||||
)
|
||||
},
|
||||
cfi = cfi,
|
||||
isExistingHighlight = true
|
||||
)
|
||||
}), "HighlightBridge")
|
||||
|
||||
customMenuState = CustomMenuState(
|
||||
selectedText = text,
|
||||
selectionBounds = rect,
|
||||
finishActionModeCallback = {
|
||||
localWebViewRef?.evaluateJavascript(
|
||||
"javascript:if(window.getSelection) window.getSelection().removeAllRanges();",
|
||||
null
|
||||
)
|
||||
},
|
||||
cfi = cfi,
|
||||
isExistingHighlight = true
|
||||
)
|
||||
}
|
||||
}
|
||||
), "HighlightBridge"
|
||||
)
|
||||
|
||||
addJavascriptInterface(
|
||||
AutoScrollJsBridge {
|
||||
onAutoScrollChapterEnd()
|
||||
this.post { onAutoScrollChapterEnd() }
|
||||
}, "AutoScrollBridge"
|
||||
)
|
||||
|
||||
|
|
@ -602,15 +613,17 @@ fun ChapterWebView(
|
|||
}
|
||||
addJavascriptInterface(
|
||||
CfiJsBridge(
|
||||
onCfiReady = { cfi -> currentOnCfiGenerated(cfi) },
|
||||
onCfiForBookmarkReady = { cfi -> currentOnBookmarkCfiGenerated(cfi) },
|
||||
onScrollFinishedCallback = { success ->
|
||||
currentOnScrollFinished(success)
|
||||
}), "CfiBridge")
|
||||
onCfiReady = { cfi -> this.post { currentOnCfiGenerated(cfi) } },
|
||||
onCfiForBookmarkReady = { cfi -> this.post { currentOnBookmarkCfiGenerated(cfi) } },
|
||||
onScrollFinishedCallback = { success ->
|
||||
this.post { currentOnScrollFinished(success) }
|
||||
}
|
||||
), "CfiBridge"
|
||||
)
|
||||
|
||||
addJavascriptInterface(
|
||||
SnippetJsBridge { cfi, snippet ->
|
||||
currentOnSnippetForBookmarkReady(cfi, snippet)
|
||||
this.post { currentOnSnippetForBookmarkReady(cfi, snippet) }
|
||||
}, "SnippetBridge"
|
||||
)
|
||||
addJavascriptInterface(TtsJsBridge(ttsScope, onTtsTextReady), "TtsBridge")
|
||||
|
|
|
|||
|
|
@ -779,7 +779,7 @@ fun EpubReaderHost(
|
|||
|
||||
var currentChapterIndex by rememberSaveable(epubBook.title) {
|
||||
mutableIntStateOf(
|
||||
initialLocator?.chapterIndex?.coerceIn(0, chapters.size - 1) ?: 0
|
||||
initialLocator?.chapterIndex?.coerceIn(0, max(0, chapters.size - 1)) ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1353,7 +1353,8 @@ fun EpubReaderHost(
|
|||
view = view,
|
||||
showBars = showBars,
|
||||
initialIsAppearanceLightStatusBars = initialIsAppearanceLightStatusBars,
|
||||
initialSystemBarsBehavior = initialSystemBarsBehavior
|
||||
initialSystemBarsBehavior = initialSystemBarsBehavior,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
|
||||
var isPagerInitialized by remember(initialLocator) { mutableStateOf(initialLocator == null) }
|
||||
|
|
@ -2249,7 +2250,7 @@ fun EpubReaderHost(
|
|||
containerFocusRequester.requestFocus()
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - lastScrollHideTime < 250) {
|
||||
if (System.currentTimeMillis() - lastScrollHideTime < 400) {
|
||||
Timber.d("Ignoring tap toggle because bars were just hidden by scroll (sloppy tap).")
|
||||
} else {
|
||||
if (showBars || showFormatAdjustmentBars) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import timber.log.Timber
|
|||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -42,11 +41,9 @@ fun EpubReaderSystemUiController(
|
|||
view: View,
|
||||
showBars: Boolean,
|
||||
initialIsAppearanceLightStatusBars: Boolean,
|
||||
initialSystemBarsBehavior: Int
|
||||
initialSystemBarsBehavior: Int,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
|
||||
// 1. Handle Immersive Mode (Enter/Exit)
|
||||
DisposableEffect(window, view, initialIsAppearanceLightStatusBars, initialSystemBarsBehavior) {
|
||||
if (window == null) {
|
||||
Timber.w("Window is null, cannot control system UI.")
|
||||
|
|
@ -68,7 +65,6 @@ fun EpubReaderSystemUiController(
|
|||
}
|
||||
}
|
||||
|
||||
// 2. Handle Status Bar Appearance (Dark/Light theme)
|
||||
LaunchedEffect(window, view, isDarkTheme) {
|
||||
if (window != null) {
|
||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||
|
|
@ -76,7 +72,6 @@ fun EpubReaderSystemUiController(
|
|||
}
|
||||
}
|
||||
|
||||
// 3. Handle Show/Hide Bars dynamically
|
||||
LaunchedEffect(showBars, window, view) {
|
||||
if (window != null) {
|
||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue