General improvements (#134)

* Add visual options and seamless transitions to the EPUB reader

* Add support for DOCX file format

* Refine padding and status bar inset handling in Epub pagination mode

* Add file size parameter for recent files and sorting

* Refactor book data cleanup logic into a centralized method

* Bump version to 1.0.40(41)
This commit is contained in:
Aryan 2026-03-30 22:44:26 +05:30 committed by GitHub
parent 15264a31ae
commit 6fd6dd5609
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 623 additions and 134 deletions

View file

@ -460,6 +460,7 @@ fun EpubReaderHost(
val searchFocusRequester = remember { FocusRequester() }
val containerFocusRequester = remember { FocusRequester() }
var isNavigatingToPosition by remember { mutableStateOf(false) }
var isSeamlessTransitioning by remember { mutableStateOf(false) }
var isPageSliderVisible by remember { mutableStateOf(false) }
var sliderCurrentPage by remember { mutableFloatStateOf(0f) }
@ -476,6 +477,11 @@ fun EpubReaderHost(
val snackbarHostState = remember { SnackbarHostState() }
var systemUiMode by remember { mutableStateOf(loadSystemUiMode(context)) }
var pageInfoMode by remember { mutableStateOf(loadPageInfoMode(context)) }
var pullToTurnEnabled by remember { mutableStateOf(loadPullToTurn(context)) }
var showVisualOptionsSheet by remember { mutableStateOf(false) }
var volumeScrollEnabled by remember {
mutableStateOf(loadVolumeScrollSetting(context))
}
@ -1354,7 +1360,8 @@ fun EpubReaderHost(
showBars = showBars,
initialIsAppearanceLightStatusBars = initialIsAppearanceLightStatusBars,
initialSystemBarsBehavior = initialSystemBarsBehavior,
isDarkTheme = isDarkTheme
isDarkTheme = isDarkTheme,
systemUiMode = systemUiMode
)
var isPagerInitialized by remember(initialLocator) { mutableStateOf(initialLocator == null) }
@ -1433,6 +1440,17 @@ fun EpubReaderHost(
}
}
val pageInfoBottomPadding by androidx.compose.animation.core.animateDpAsState(
targetValue = if (showBars && pageInfoMode == PageInfoMode.SYNC) 45.dp else 0.dp,
label = "PageInfoBottomPadding"
)
val isPageInfoVisible = when (pageInfoMode) {
PageInfoMode.DEFAULT -> !showBars
PageInfoMode.SYNC -> showBars
PageInfoMode.HIDDEN -> false
}
LaunchedEffect(bookmarks, paginator) {
paginator ?: return@LaunchedEffect
val bookPaginator = paginator as? BookPaginator
@ -1974,11 +1992,37 @@ fun EpubReaderHost(
snackbarHost = { SnackbarHost(snackbarHostState) },
contentWindowInsets = WindowInsets.statusBars,
) { scaffoldPaddingValues ->
val currentTopPadding = scaffoldPaddingValues.calculateTopPadding()
var stableTopPadding by remember { mutableStateOf(0.dp) }
if (currentTopPadding > stableTopPadding) {
stableTopPadding = currentTopPadding
}
val effectiveTopPadding = if (currentRenderMode == RenderMode.PAGINATED) {
if (systemUiMode == SystemUiMode.HIDDEN) {
0.dp
} else {
val insets = androidx.core.view.ViewCompat.getRootWindowInsets(view)
val ignoringVisibilityTopPx = insets?.getInsetsIgnoringVisibility(androidx.core.view.WindowInsetsCompat.Type.statusBars())?.top ?: 0
val ignoringVisibilityTop = with(density) { ignoringVisibilityTopPx.toDp() }
if (ignoringVisibilityTop > 0.dp) {
ignoringVisibilityTop
} else if (stableTopPadding > 0.dp) {
stableTopPadding
} else {
24.dp
}
}
} else {
currentTopPadding
}
Box(
modifier = Modifier
.fillMaxSize()
.background(effectiveBg)
.padding(scaffoldPaddingValues)
.padding(top = effectiveTopPadding)
.focusRequester(containerFocusRequester)
.focusable()
.volumeScrollHandler(
@ -2031,10 +2075,16 @@ fun EpubReaderHost(
) {
when (currentRenderMode) {
RenderMode.VERTICAL_SCROLL -> {
val contentBottomPadding = if (showBars || showFormatAdjustmentBars) {
0.dp
} else {
if (pageInfoMode == PageInfoMode.DEFAULT) PAGE_INFO_BAR_HEIGHT else 0.dp
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(bottom = if (showBars || showFormatAdjustmentBars) 0.dp else PAGE_INFO_BAR_HEIGHT)
.padding(bottom = contentBottomPadding)
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
.testTag("ReaderContainer")
) {
@ -2049,12 +2099,16 @@ fun EpubReaderHost(
AnimatedContent(
targetState = currentChapterIndex,
transitionSpec = {
if (targetState > initialState) {
(slideInVertically { height -> height } + fadeIn())
.togetherWith(slideOutVertically { height -> -height } + fadeOut())
if (!pullToTurnEnabled) {
fadeIn(animationSpec = tween(150)) togetherWith fadeOut(animationSpec = tween(150))
} else {
(slideInVertically { height -> -height } + fadeIn())
.togetherWith(slideOutVertically { height -> height } + fadeOut())
if (targetState > initialState) {
(slideInVertically { height -> height } + fadeIn())
.togetherWith(slideOutVertically { height -> -height } + fadeOut())
} else {
(slideInVertically { height -> -height } + fadeIn())
.togetherWith(slideOutVertically { height -> height } + fadeOut())
}
}
},
label = "ChapterChangeAnimation",
@ -2159,9 +2213,7 @@ fun EpubReaderHost(
.mapNotNull { it.fragmentId }
}
@Suppress("KotlinConstantConditions",
"ControlFlowWithEmptyBody"
)
@Suppress("ControlFlowWithEmptyBody")
ChapterWebView(
key = chapterKeyForWebView,
chapterTitle = chapterToRender.title,
@ -2292,19 +2344,48 @@ fun EpubReaderHost(
}
},
onOverScrollTop = { dragAmount ->
if (targetChapterIndex > 0) {
pullToPrevProgress =
dragAmount / dragThresholdPx
if (pullToTurnEnabled) {
if (targetChapterIndex > 0) {
pullToPrevProgress = dragAmount / dragThresholdPx
}
} else {
if (targetChapterIndex > 0 && dragAmount > 20f && !isSeamlessTransitioning) {
isSeamlessTransitioning = true
webViewRefForTts?.evaluateJavascript("javascript:CfiBridge.onCfiExtracted(window.getCurrentCfi());", null)
scope.launch {
delay(20)
initialScrollTargetForChapter = ChapterScrollPosition.END
currentChapterIndex--
if (showBars) showBars = false
delay(300)
isSeamlessTransitioning = false
}
}
}
},
onOverScrollBottom = { dragAmount ->
if (targetChapterIndex < chapters.size - 1) {
pullToNextProgress =
dragAmount / dragThresholdPx
if (pullToTurnEnabled) {
if (targetChapterIndex < chapters.size - 1) {
pullToNextProgress = dragAmount / dragThresholdPx
}
} else {
if (targetChapterIndex < chapters.size - 1 && dragAmount > 20f && !isSeamlessTransitioning) {
isSeamlessTransitioning = true
webViewRefForTts?.evaluateJavascript("javascript:CfiBridge.onCfiExtracted(window.getCurrentCfi());", null)
scope.launch {
delay(20)
initialScrollTargetForChapter = ChapterScrollPosition.START
currentScrollYPosition = 0
currentChapterIndex++
if (showBars) showBars = false
delay(300)
isSeamlessTransitioning = false
}
}
}
},
onReleaseOverScrollTop = {
if (targetChapterIndex > 0 && pullToPrevProgress >= 1.0f) {
if (pullToTurnEnabled && targetChapterIndex > 0 && pullToPrevProgress >= 1.0f) {
Timber.d("Swipe-up triggered. Saving position before changing to previous chapter."
)
webViewRefForTts?.evaluateJavascript(
@ -2324,7 +2405,7 @@ fun EpubReaderHost(
pullToPrevProgress = 0f
},
onReleaseOverScrollBottom = {
if (targetChapterIndex < chapters.size - 1 && pullToNextProgress >= 1.0f) {
if (pullToTurnEnabled && targetChapterIndex < chapters.size - 1 && pullToNextProgress >= 1.0f) {
Timber.d("Swipe-down triggered. Saving position before changing to next chapter."
)
webViewRefForTts?.evaluateJavascript(
@ -2672,7 +2753,7 @@ fun EpubReaderHost(
}
}
if (currentChapterIndex > 0) {
if (pullToTurnEnabled && currentChapterIndex > 0) {
ChapterChangeIndicator(
text = "Release for Previous Chapter",
progress = pullToPrevProgress,
@ -2683,7 +2764,7 @@ fun EpubReaderHost(
)
}
if (currentChapterIndex < chapters.size - 1) {
if (pullToTurnEnabled && currentChapterIndex < chapters.size - 1) {
ChapterChangeIndicator(
text = "Release for Next Chapter",
progress = pullToNextProgress,
@ -2698,13 +2779,14 @@ fun EpubReaderHost(
}
RenderMode.PAGINATED -> {
val contentBottomPadding = if (pageInfoMode != PageInfoMode.HIDDEN) PAGE_INFO_BAR_HEIGHT else 0.dp
BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
.padding(bottom = PAGE_INFO_BAR_HEIGHT)
.padding(bottom = contentBottomPadding)
.testTag("ReaderContainer")
) {
@Suppress("KotlinConstantConditions")
PaginatedReaderScreen(
book = epubBook,
isDarkTheme = isDarkTheme,
@ -2992,7 +3074,7 @@ fun EpubReaderHost(
// Page Info Bar (Vertical)
AnimatedVisibility(
visible = renderMode == RenderMode.VERTICAL_SCROLL && !showBars,
visible = renderMode == RenderMode.VERTICAL_SCROLL && isPageInfoVisible,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(200)),
modifier = Modifier.align(Alignment.BottomCenter)
@ -3002,7 +3084,7 @@ fun EpubReaderHost(
.fillMaxWidth()
.height(PAGE_INFO_BAR_HEIGHT)
.background(infoBarBgColor)
.padding(bottom = bottomPadding)
.padding(bottom = bottomPadding + pageInfoBottomPadding)
.padding(horizontal = 16.dp),
contentAlignment = Alignment.Center
) {
@ -3035,7 +3117,7 @@ fun EpubReaderHost(
// Page Info Bar (Paginated)
AnimatedVisibility(
visible = renderMode == RenderMode.PAGINATED && paginator != null && !showBars && paginatedPagerState.pageCount > 0,
visible = renderMode == RenderMode.PAGINATED && paginator != null && isPageInfoVisible && paginatedPagerState.pageCount > 0,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(200)),
modifier = Modifier.align(Alignment.BottomCenter)
@ -3045,7 +3127,7 @@ fun EpubReaderHost(
.fillMaxWidth()
.height(PAGE_INFO_BAR_HEIGHT)
.background(infoBarBgColor)
.padding(bottom = bottomPadding)
.padding(bottom = bottomPadding + pageInfoBottomPadding)
.padding(horizontal = 16.dp),
contentAlignment = Alignment.Center
) {
@ -3400,6 +3482,7 @@ fun EpubReaderHost(
onOpenDictionarySettings = { showDictionarySettingsSheet = true },
onOpenDeviceVoiceSettings = { showDeviceVoiceSettingsSheet = true },
onOpenThemeSettings = { showThemePanel = true },
onOpenVisualOptions = { showVisualOptionsSheet = true },
onToggleReflow = if (onToggleReflow != null) {
{
val activeChapter = if (currentRenderMode == RenderMode.PAGINATED) {
@ -3950,6 +4033,27 @@ fun EpubReaderHost(
)
}
if (showVisualOptionsSheet) {
VisualOptionsSheet(
systemUiMode = systemUiMode,
onSystemUiModeChange = {
systemUiMode = it
saveSystemUiMode(context, it)
},
pageInfoMode = pageInfoMode,
onPageInfoModeChange = {
pageInfoMode = it
savePageInfoMode(context, it)
},
pullToTurnEnabled = pullToTurnEnabled,
onPullToTurnChange = {
pullToTurnEnabled = it
savePullToTurn(context, it)
},
onDismiss = { showVisualOptionsSheet = false }
)
}
if (showFontSelectionSheet) {
ModalBottomSheet(
onDismissRequest = { showFontSelectionSheet = false },