Epub improvements (#160)
* Improved navigation and search result highlighting in the EPUB reader * refactor: implement JIT chunk restoration for robust navigation - Fixes search and bookmark navigation failing in same-chapter transitions. - Introduced JIT (Just-in-Time) HTML restoration in `epub_reader.js` to re-populate virtualized chunks before CFI resolution or search scrolling. - Synchronized search highlighting and user highlights within newly-restored chunk segments. - Improved search navigation accuracy by mapping occurrences to relative chunk indices. * Implemented a custom text selection engine for the paginated EPUB reader to support cross-page selection and improved handle interaction. * Implemented management of external file behavior and improved library filtering. * Added support for toolbar customization in the EPUB reader. * Added support for toolbar customization in the PDF reader. * fix: rendering during auto-scroll and navigation on long pages * Added a "Scroll to Top" feature to the auto-scroll controls in both EPUB and PDF readers.
This commit is contained in:
parent
db7e05ce63
commit
6a60aec0ef
17 changed files with 3045 additions and 2060 deletions
|
|
@ -577,6 +577,11 @@ fun ChapterWebView(
|
|||
)
|
||||
}
|
||||
|
||||
message.startsWith("NavDiag:") -> {
|
||||
Timber.tag("NavDiag")
|
||||
.d("JS -> ${message.substringAfter("NavDiag: ")}")
|
||||
}
|
||||
|
||||
message.startsWith("AutoScrollDiagnosis") -> {
|
||||
Timber.d(
|
||||
"JS -> ${message.substringAfter("AutoScrollDiagnosis: ")}"
|
||||
|
|
@ -726,13 +731,13 @@ fun ChapterWebView(
|
|||
|
||||
if (!initialCfi.isNullOrBlank()) {
|
||||
val cfiJsCommand = "javascript:window.scrollToCfi('$initialCfi');"
|
||||
Timber.tag("POS_DIAG").d("WebView onPageFinished: Triggering scroll to initialCfi: $initialCfi")
|
||||
Timber.tag("NavDiag").d("WebView onPageFinished: Triggering scroll to initialCfi: $initialCfi")
|
||||
view?.evaluateJavascript(cfiJsCommand) {
|
||||
onChapterInitiallyScrolled()
|
||||
scrollActionTaken = true
|
||||
}
|
||||
} else if (!initialFragmentId.isNullOrBlank()) {
|
||||
Timber.d("WebView onPageFinished: Scrolling to Element ID: $initialFragmentId")
|
||||
Timber.tag("NavDiag").d("WebView onPageFinished: Scrolling to Element ID: $initialFragmentId")
|
||||
view?.evaluateJavascript(
|
||||
"javascript:var el = document.getElementById('$initialFragmentId'); if(el) { el.scrollIntoView(); } else { console.log('Element not found: $initialFragmentId'); }",
|
||||
null
|
||||
|
|
@ -744,9 +749,7 @@ fun ChapterWebView(
|
|||
ChapterScrollPosition.END -> "javascript:window.scrollToChapterEnd();"
|
||||
else -> "javascript:window.scrollToChapterStart();"
|
||||
}
|
||||
Timber.d(
|
||||
"WebView onPageFinished: Executing initial scroll to target: $initialScrollTarget"
|
||||
)
|
||||
Timber.tag("NavDiag").d("WebView onPageFinished: Executing initial scroll to target: $initialScrollTarget")
|
||||
view?.evaluateJavascript(scrollJsCommand) {
|
||||
onChapterInitiallyScrolled()
|
||||
scrollActionTaken = true
|
||||
|
|
@ -754,17 +757,13 @@ fun ChapterWebView(
|
|||
} else if (initialPageScrollY != null && initialPageScrollY > 0) {
|
||||
val scrollJsCommand =
|
||||
"javascript:window.scrollToSpecificY($initialPageScrollY);"
|
||||
Timber.d(
|
||||
"WebView onPageFinished: Executing initial scroll to Y: $initialPageScrollY"
|
||||
)
|
||||
Timber.tag("NavDiag").d("WebView onPageFinished: Executing initial scroll to Y: $initialPageScrollY")
|
||||
view?.evaluateJavascript(scrollJsCommand) {
|
||||
onChapterInitiallyScrolled()
|
||||
scrollActionTaken = true
|
||||
}
|
||||
} else {
|
||||
Timber.d(
|
||||
"WebView onPageFinished: No specific scroll, defaulting to start."
|
||||
)
|
||||
Timber.tag("NavDiag").d("WebView onPageFinished: No specific scroll, defaulting to start.")
|
||||
view?.evaluateJavascript("javascript:window.scrollToChapterStart();") {
|
||||
onChapterInitiallyScrolled()
|
||||
scrollActionTaken = true
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ import androidx.annotation.RequiresApi
|
|||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
|
|
@ -69,6 +74,7 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.ChevronLeft
|
||||
import androidx.compose.material.icons.filled.ChevronRight
|
||||
|
|
@ -131,6 +137,26 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
enum class ReaderTool(val title: String, val category: String) {
|
||||
DICTIONARY("External Apps", "Top Bar"),
|
||||
THEME("Theme Settings", "Top Bar"),
|
||||
SLIDER("Navigation Slider", "Bottom Bar"),
|
||||
TOC("Sidebar", "Bottom Bar"),
|
||||
FORMAT("Text Formatting", "Bottom Bar"),
|
||||
SEARCH("Search", "Bottom Bar"),
|
||||
AI_FEATURES("AI Features", "Bottom Bar"),
|
||||
TTS_CONTROLS("TTS Controls", "Bottom Bar"),
|
||||
READING_MODE("Reading Mode", "Overflow Menu"),
|
||||
BOOKMARK("Bookmark", "Overflow Menu"),
|
||||
TAP_TO_TURN("Tap to Turn Pages", "Overflow Menu"),
|
||||
VOLUME_SCROLL("Volume Button Scrolling", "Overflow Menu"),
|
||||
PAGE_TURN_ANIM("Realistic Page Turns", "Overflow Menu"),
|
||||
KEEP_SCREEN_ON("Keep Screen On", "Overflow Menu"),
|
||||
VISUAL_OPTIONS("Visual Options", "Overflow Menu"),
|
||||
AUTO_SCROLL("Auto Scroll", "Overflow Menu"),
|
||||
TTS_SETTINGS("TTS Voice Settings", "Overflow Menu")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EpubReaderTopBar(
|
||||
isVisible: Boolean,
|
||||
|
|
@ -158,6 +184,8 @@ fun EpubReaderTopBar(
|
|||
onOpenThemeSettings: () -> Unit,
|
||||
onOpenVisualOptions: () -> Unit,
|
||||
searchFocusRequester: androidx.compose.ui.focus.FocusRequester,
|
||||
hiddenTools: Set<String>,
|
||||
onCustomizeTools: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onToggleReflow: (() -> Unit)? = null,
|
||||
onDeleteReflow: (() -> Unit)? = null,
|
||||
|
|
@ -204,22 +232,26 @@ fun EpubReaderTopBar(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_dictionary),
|
||||
description = stringResource(R.string.tooltip_dictionary_desc),
|
||||
onClick = onOpenDictionarySettings
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.dictionary),
|
||||
contentDescription = stringResource(R.string.content_desc_dictionary_settings)
|
||||
)
|
||||
if (!hiddenTools.contains(ReaderTool.DICTIONARY.name)) {
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_dictionary),
|
||||
description = stringResource(R.string.tooltip_dictionary_desc),
|
||||
onClick = onOpenDictionarySettings
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.dictionary),
|
||||
contentDescription = stringResource(R.string.content_desc_dictionary_settings)
|
||||
)
|
||||
}
|
||||
}
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_theme),
|
||||
description = stringResource(R.string.tooltip_theme_desc),
|
||||
onClick = onOpenThemeSettings
|
||||
) {
|
||||
Icon(painter = painterResource(id = R.drawable.palette), contentDescription = stringResource(R.string.tooltip_theme_desc))
|
||||
if (!hiddenTools.contains(ReaderTool.THEME.name)) {
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_theme),
|
||||
description = stringResource(R.string.tooltip_theme_desc),
|
||||
onClick = onOpenThemeSettings
|
||||
) {
|
||||
Icon(painter = painterResource(id = R.drawable.palette), contentDescription = stringResource(R.string.tooltip_theme_desc))
|
||||
}
|
||||
}
|
||||
Box {
|
||||
var showMoreMenu by remember { mutableStateOf(false) }
|
||||
|
|
@ -235,6 +267,18 @@ fun EpubReaderTopBar(
|
|||
expanded = showMoreMenu,
|
||||
onDismissRequest = { showMoreMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Customize Toolbar") },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onCustomizeTools()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Settings, contentDescription = null, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
if (onToggleReflow != null) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_view_original_pdf)) },
|
||||
|
|
@ -274,125 +318,175 @@ fun EpubReaderTopBar(
|
|||
)
|
||||
}
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_reading_mode_vertical)) },
|
||||
enabled = !isTtsActive,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onChangeRenderMode(RenderMode.VERTICAL_SCROLL)
|
||||
},
|
||||
trailingIcon = { if (currentRenderMode == RenderMode.VERTICAL_SCROLL) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_selected)) }
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_reading_mode_paginated)) },
|
||||
enabled = !isTtsActive,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onChangeRenderMode(RenderMode.PAGINATED)
|
||||
},
|
||||
trailingIcon = { if (currentRenderMode == RenderMode.PAGINATED) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_selected)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = { Text(if (isBookmarked) stringResource(R.string.menu_remove_bookmark) else stringResource(R.string.menu_bookmark_this_page)) },
|
||||
onClick = {
|
||||
if (!hiddenTools.contains(ReaderTool.READING_MODE.name)) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_reading_mode_vertical)) },
|
||||
enabled = !isTtsActive,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onChangeRenderMode(RenderMode.VERTICAL_SCROLL)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (currentRenderMode == RenderMode.VERTICAL_SCROLL) Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.content_desc_selected)
|
||||
)
|
||||
})
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_reading_mode_paginated)) },
|
||||
enabled = !isTtsActive,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onChangeRenderMode(RenderMode.PAGINATED)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (currentRenderMode == RenderMode.PAGINATED) Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.content_desc_selected)
|
||||
)
|
||||
})
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (!hiddenTools.contains(ReaderTool.BOOKMARK.name)) {
|
||||
DropdownMenuItem(text = {
|
||||
Text(
|
||||
if (isBookmarked) stringResource(R.string.menu_remove_bookmark) else stringResource(
|
||||
R.string.menu_bookmark_this_page
|
||||
)
|
||||
)
|
||||
}, onClick = {
|
||||
showMoreMenu = false
|
||||
onToggleBookmark()
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_tap_to_turn_pages)) },
|
||||
enabled = currentRenderMode == RenderMode.PAGINATED,
|
||||
onClick = {
|
||||
onToggleTapToNavigate(!tapToNavigateEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = { if (tapToNavigateEnabled) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_enabled)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
})
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (!hiddenTools.contains(ReaderTool.TAP_TO_TURN.name)) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_tap_to_turn_pages)) },
|
||||
enabled = currentRenderMode == RenderMode.PAGINATED,
|
||||
onClick = {
|
||||
onToggleTapToNavigate(!tapToNavigateEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = {
|
||||
if (tapToNavigateEnabled) Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.content_desc_enabled)
|
||||
)
|
||||
})
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (!hiddenTools.contains(ReaderTool.VOLUME_SCROLL.name)) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
if (currentRenderMode == RenderMode.VERTICAL_SCROLL) stringResource(R.string.menu_volume_button_scrolling)
|
||||
if (currentRenderMode == RenderMode.VERTICAL_SCROLL) stringResource(
|
||||
R.string.menu_volume_button_scrolling
|
||||
)
|
||||
else stringResource(R.string.menu_volume_button_page_turn)
|
||||
)
|
||||
},
|
||||
enabled = true,
|
||||
onClick = {
|
||||
onToggleVolumeScroll(!volumeScrollEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = { if (volumeScrollEnabled) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_enabled)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_realistic_page_turns)) },
|
||||
enabled = currentRenderMode == RenderMode.PAGINATED,
|
||||
onClick = {
|
||||
onTogglePageTurnAnimation(!isPageTurnAnimationEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = { if (isPageTurnAnimationEnabled) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_enabled)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_keep_screen_on)) },
|
||||
onClick = {
|
||||
onToggleKeepScreenOn(!isKeepScreenOn)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = { if (isKeepScreenOn) Icon(Icons.Default.Check, contentDescription = stringResource(R.string.content_desc_enabled)) }
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_visual_options)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenVisualOptions()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Visibility, contentDescription = null, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_auto_scroll)) },
|
||||
enabled = !isTtsActive && currentRenderMode == RenderMode.VERTICAL_SCROLL,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onStartAutoScroll()
|
||||
}
|
||||
)
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_tts_voice_settings)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenDeviceVoiceSettings()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.GraphicEq, contentDescription = null, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
enabled = true,
|
||||
onClick = {
|
||||
onToggleVolumeScroll(!volumeScrollEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = {
|
||||
if (volumeScrollEnabled) Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.content_desc_enabled)
|
||||
)
|
||||
})
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (!hiddenTools.contains(ReaderTool.PAGE_TURN_ANIM.name)) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_tts_settings_debug)) },
|
||||
text = { Text(stringResource(R.string.menu_realistic_page_turns)) },
|
||||
enabled = currentRenderMode == RenderMode.PAGINATED,
|
||||
onClick = {
|
||||
onTogglePageTurnAnimation(!isPageTurnAnimationEnabled)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = {
|
||||
if (isPageTurnAnimationEnabled) Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.content_desc_enabled)
|
||||
)
|
||||
})
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (!hiddenTools.contains(ReaderTool.KEEP_SCREEN_ON.name)) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_keep_screen_on)) },
|
||||
onClick = {
|
||||
onToggleKeepScreenOn(!isKeepScreenOn)
|
||||
showMoreMenu = false
|
||||
},
|
||||
trailingIcon = {
|
||||
if (isKeepScreenOn) Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.content_desc_enabled)
|
||||
)
|
||||
})
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (!hiddenTools.contains(ReaderTool.VISUAL_OPTIONS.name)) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_visual_options)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenTtsSettings()
|
||||
onOpenVisualOptions()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(painter = painterResource(id = R.drawable.text_to_speech), contentDescription = null, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
)
|
||||
Icon(
|
||||
Icons.Default.Visibility,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
})
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (!hiddenTools.contains(ReaderTool.AUTO_SCROLL.name)) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_auto_scroll)) },
|
||||
enabled = !isTtsActive && currentRenderMode == RenderMode.VERTICAL_SCROLL,
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onStartAutoScroll()
|
||||
})
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (!hiddenTools.contains(ReaderTool.TTS_SETTINGS.name)) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_tts_voice_settings)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenDeviceVoiceSettings()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
Icons.Default.GraphicEq,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
})
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_tts_settings_debug)) },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenTtsSettings()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.text_to_speech),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -418,6 +512,7 @@ fun EpubReaderBottomBar(
|
|||
onRecap: () -> Unit,
|
||||
onToggleTts: () -> Unit,
|
||||
onPlayPauseTts: () -> Unit,
|
||||
hiddenTools: Set<String>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
|
|
@ -439,107 +534,131 @@ fun EpubReaderBottomBar(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceAround
|
||||
) {
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_slider),
|
||||
description = stringResource(R.string.tooltip_slider_desc),
|
||||
onClick = onOpenSlider,
|
||||
enabled = currentRenderMode != RenderMode.VERTICAL_SCROLL
|
||||
) {
|
||||
Icon(painter = painterResource(id = R.drawable.slider), contentDescription = stringResource(R.string.content_desc_navigate_slider))
|
||||
if (!hiddenTools.contains(ReaderTool.SLIDER.name)) {
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_slider),
|
||||
description = stringResource(R.string.tooltip_slider_desc),
|
||||
onClick = onOpenSlider,
|
||||
enabled = currentRenderMode != RenderMode.VERTICAL_SCROLL
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.slider),
|
||||
contentDescription = stringResource(R.string.content_desc_navigate_slider)
|
||||
)
|
||||
}
|
||||
}
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_toc),
|
||||
description = stringResource(R.string.tooltip_toc_desc),
|
||||
onClick = onOpenDrawer
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Menu, contentDescription = stringResource(R.string.content_desc_chapters_menu))
|
||||
if (!hiddenTools.contains(ReaderTool.TOC.name)) {
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_toc),
|
||||
description = stringResource(R.string.tooltip_toc_desc),
|
||||
onClick = onOpenDrawer
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = stringResource(R.string.content_desc_chapters_menu)
|
||||
)
|
||||
}
|
||||
}
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_format),
|
||||
description = stringResource(R.string.tooltip_format_desc),
|
||||
onClick = onToggleFormat
|
||||
) {
|
||||
Icon(painter = painterResource(id = R.drawable.format_size), contentDescription = stringResource(R.string.content_desc_text_formatting))
|
||||
if (!hiddenTools.contains(ReaderTool.FORMAT.name)) {
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_format),
|
||||
description = stringResource(R.string.tooltip_format_desc),
|
||||
onClick = onToggleFormat
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.format_size),
|
||||
contentDescription = stringResource(R.string.content_desc_text_formatting)
|
||||
)
|
||||
}
|
||||
}
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_search),
|
||||
description = stringResource(R.string.tooltip_search_desc),
|
||||
onClick = onToggleSearch
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Search, contentDescription = stringResource(R.string.tooltip_search))
|
||||
if (!hiddenTools.contains(ReaderTool.SEARCH.name)) {
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_search),
|
||||
description = stringResource(R.string.tooltip_search_desc),
|
||||
onClick = onToggleSearch
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Search,
|
||||
contentDescription = stringResource(R.string.tooltip_search)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
|
||||
if (BuildConfig.FLAVOR != "oss") {
|
||||
Box {
|
||||
var showAiFeaturesMenu by remember { mutableStateOf(false) }
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_ai),
|
||||
description = stringResource(R.string.tooltip_ai_desc),
|
||||
onClick = { showAiFeaturesMenu = true }
|
||||
) {
|
||||
Icon(painter = painterResource(id = R.drawable.ai), contentDescription = "AI Features")
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showAiFeaturesMenu,
|
||||
onDismissRequest = { showAiFeaturesMenu = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_chapter_summarization)) },
|
||||
onClick = {
|
||||
showAiFeaturesMenu = false
|
||||
onSummarize()
|
||||
}
|
||||
)
|
||||
if (BuildConfig.DEBUG && isProUser) {
|
||||
HorizontalDivider()
|
||||
if (!hiddenTools.contains(ReaderTool.AI_FEATURES.name)) {
|
||||
@Suppress(
|
||||
"KotlinConstantConditions",
|
||||
"SimplifyBooleanWithConstants"
|
||||
) if (BuildConfig.FLAVOR != "oss") {
|
||||
Box {
|
||||
var showAiFeaturesMenu by remember { mutableStateOf(false) }
|
||||
TooltipIconButton(
|
||||
text = stringResource(R.string.tooltip_ai),
|
||||
description = stringResource(R.string.tooltip_ai_desc),
|
||||
onClick = { showAiFeaturesMenu = true }) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ai),
|
||||
contentDescription = "AI Features"
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showAiFeaturesMenu,
|
||||
onDismissRequest = { showAiFeaturesMenu = false }) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_recap_beta)) },
|
||||
text = { Text(stringResource(R.string.menu_chapter_summarization)) },
|
||||
onClick = {
|
||||
showAiFeaturesMenu = false
|
||||
onRecap()
|
||||
}
|
||||
)
|
||||
onSummarize()
|
||||
})
|
||||
if (BuildConfig.DEBUG && isProUser) {
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.menu_recap_beta)) },
|
||||
onClick = {
|
||||
showAiFeaturesMenu = false
|
||||
onRecap()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Box {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
TooltipIconButton(
|
||||
text = if (isTtsSessionActive)
|
||||
stringResource(R.string.tooltip_tts_stop)
|
||||
else
|
||||
stringResource(R.string.tooltip_tts_start),
|
||||
description = if (isTtsSessionActive)
|
||||
stringResource(R.string.tooltip_tts_stop_desc)
|
||||
else
|
||||
stringResource(R.string.tooltip_tts_start_desc),
|
||||
onClick = onToggleTts
|
||||
) {
|
||||
Icon(
|
||||
painter = if (isTtsSessionActive) painterResource(id = R.drawable.close) else painterResource(id = R.drawable.text_to_speech),
|
||||
contentDescription = if (isTtsSessionActive) stringResource(R.string.content_desc_stop_tts) else stringResource(R.string.content_desc_start_tts)
|
||||
)
|
||||
}
|
||||
if (isTtsSessionActive) {
|
||||
|
||||
if (!hiddenTools.contains(ReaderTool.TTS_CONTROLS.name)) {
|
||||
Box {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
TooltipIconButton(
|
||||
text = if (ttsState.isPlaying)
|
||||
stringResource(R.string.tooltip_tts_pause)
|
||||
else
|
||||
stringResource(R.string.tooltip_tts_resume),
|
||||
description = if (ttsState.isPlaying)
|
||||
stringResource(R.string.tooltip_tts_pause_desc)
|
||||
else
|
||||
stringResource(R.string.tooltip_tts_resume_desc),
|
||||
onClick = onPlayPauseTts,
|
||||
enabled = !ttsState.isLoading
|
||||
text = if (isTtsSessionActive) stringResource(R.string.tooltip_tts_stop)
|
||||
else stringResource(R.string.tooltip_tts_start),
|
||||
description = if (isTtsSessionActive) stringResource(R.string.tooltip_tts_stop_desc)
|
||||
else stringResource(R.string.tooltip_tts_start_desc),
|
||||
onClick = onToggleTts
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = if (ttsState.isPlaying) R.drawable.pause else R.drawable.play),
|
||||
contentDescription = if (ttsState.isPlaying) stringResource(R.string.content_desc_pause_tts) else stringResource(R.string.content_desc_resume_tts)
|
||||
painter = if (isTtsSessionActive) painterResource(id = R.drawable.close) else painterResource(
|
||||
id = R.drawable.text_to_speech
|
||||
),
|
||||
contentDescription = if (isTtsSessionActive) stringResource(R.string.content_desc_stop_tts) else stringResource(
|
||||
R.string.content_desc_start_tts
|
||||
)
|
||||
)
|
||||
}
|
||||
if (isTtsSessionActive) {
|
||||
TooltipIconButton(
|
||||
text = if (ttsState.isPlaying) stringResource(R.string.tooltip_tts_pause)
|
||||
else stringResource(R.string.tooltip_tts_resume),
|
||||
description = if (ttsState.isPlaying) stringResource(R.string.tooltip_tts_pause_desc)
|
||||
else stringResource(R.string.tooltip_tts_resume_desc),
|
||||
onClick = onPlayPauseTts,
|
||||
enabled = !ttsState.isLoading
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = if (ttsState.isPlaying) R.drawable.pause else R.drawable.play),
|
||||
contentDescription = if (ttsState.isPlaying) stringResource(
|
||||
R.string.content_desc_pause_tts
|
||||
) else stringResource(R.string.content_desc_resume_tts)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -885,6 +1004,7 @@ fun AutoScrollControls(
|
|||
onLocalModeToggle: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isTempPaused: Boolean = false,
|
||||
onScrollToTop: (() -> Unit)? = null
|
||||
) {
|
||||
val backgroundAlpha = 0.6f
|
||||
|
||||
|
|
@ -1018,6 +1138,19 @@ fun AutoScrollControls(
|
|||
}
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
if (onScrollToTop != null) {
|
||||
IconButton(
|
||||
onClick = onScrollToTop,
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowUpward,
|
||||
contentDescription = "Scroll to Top",
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
onClick = onMusicianModeToggle,
|
||||
modifier = Modifier.size(32.dp)
|
||||
|
|
@ -1216,4 +1349,76 @@ fun AutoScrollControls(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CustomizeToolsSheet(
|
||||
hiddenTools: Set<String>,
|
||||
onUpdate: (Set<String>) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
contentWindowInsets = { WindowInsets.navigationBars }
|
||||
) {
|
||||
Column(modifier = Modifier.padding(horizontal = 24.dp).padding(bottom = 24.dp)) {
|
||||
Text(
|
||||
text = "Customize Toolbar",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Select the tools you want to keep visible. Unchecking a tool hides it from the UI to give you a distraction-free reading space.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxWidth()) {
|
||||
ReaderTool.entries.groupBy { it.category }.forEach { (category, tools) ->
|
||||
item {
|
||||
Text(
|
||||
text = category,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
items(tools) { tool ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable {
|
||||
val newSet = hiddenTools.toMutableSet()
|
||||
if (newSet.contains(tool.name)) newSet.remove(tool.name)
|
||||
else newSet.add(tool.name)
|
||||
onUpdate(newSet)
|
||||
}
|
||||
.padding(vertical = 12.dp, horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = tool.title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Switch(
|
||||
checked = !hiddenTools.contains(tool.name),
|
||||
onCheckedChange = { isVisible ->
|
||||
val newSet = hiddenTools.toMutableSet()
|
||||
if (isVisible) newSet.remove(tool.name) else newSet.add(tool.name)
|
||||
onUpdate(newSet)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -214,6 +214,17 @@ private const val AUTO_SCROLL_LOCAL_MIN_PREFIX = "auto_scroll_local_min_"
|
|||
private const val AUTO_SCROLL_LOCAL_MAX_PREFIX = "auto_scroll_local_max_"
|
||||
private const val MUSICIAN_MODE_KEY = "musician_mode_enabled"
|
||||
private const val KEEP_SCREEN_ON_KEY = "keep_screen_on_enabled"
|
||||
private const val HIDDEN_TOOLS_KEY = "hidden_reader_tools"
|
||||
|
||||
private fun saveHiddenTools(context: Context, hiddenTools: Set<String>) {
|
||||
val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
|
||||
prefs.edit { putStringSet(HIDDEN_TOOLS_KEY, hiddenTools) }
|
||||
}
|
||||
|
||||
private fun loadHiddenTools(context: Context): Set<String> {
|
||||
val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
|
||||
return prefs.getStringSet(HIDDEN_TOOLS_KEY, emptySet()) ?: emptySet()
|
||||
}
|
||||
|
||||
private fun saveKeepScreenOn(context: Context, isEnabled: Boolean) {
|
||||
val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
|
||||
|
|
@ -655,6 +666,9 @@ fun EpubReaderHost(
|
|||
mutableStateOf(loadExternalSearchPackage(context))
|
||||
}
|
||||
|
||||
var hiddenTools by remember { mutableStateOf(loadHiddenTools(context)) }
|
||||
var showCustomizeToolsSheet by remember { mutableStateOf(false) }
|
||||
|
||||
var showDictionaryUpsellDialog by remember { mutableStateOf(false) }
|
||||
var showSummarizationUpsellDialog by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -1603,6 +1617,7 @@ fun EpubReaderHost(
|
|||
}
|
||||
|
||||
fun navigateToSearchResult(index: Int) {
|
||||
Timber.tag("NavDiag").d("navigateToSearchResult index: $index")
|
||||
performSearchResultNavigation(
|
||||
index = index,
|
||||
searchState = searchState,
|
||||
|
|
@ -1613,17 +1628,36 @@ fun EpubReaderHost(
|
|||
paginator = paginator,
|
||||
coroutineScope = scope,
|
||||
onVerticalChapterChange = { chapterIdx, chunkIdx, result ->
|
||||
initialScrollTargetForChapter = ChapterScrollPosition.START
|
||||
Timber.tag("NavDiag").d("onVerticalChapterChange chapterIdx=$chapterIdx, chunkIdx=$chunkIdx, query=${result.query}")
|
||||
initialScrollTargetForChapter = null
|
||||
chunkTargetOverride = chunkIdx
|
||||
currentScrollYPosition = 0
|
||||
currentScrollHeightValue = 0
|
||||
currentChapterIndex = chapterIdx
|
||||
searchHighlightTarget = result
|
||||
loadUpToChunkIndex = chunkIdx
|
||||
},
|
||||
onVerticalScrollToResult = { _ ->
|
||||
searchHighlightTarget = null
|
||||
onVerticalScrollToResult = { result ->
|
||||
Timber.tag("NavDiag").d("onVerticalScrollToResult query=${result.query}, chunk=${result.chunkIndex}")
|
||||
val targetChunk = result.chunkIndex
|
||||
if (targetChunk >= loadedChunkCount) {
|
||||
val chunksToInject = (loadedChunkCount..targetChunk)
|
||||
chunksToInject.forEach { idx ->
|
||||
val content = chapterChunks.getOrNull(idx)
|
||||
if (content != null) {
|
||||
val escaped = escapeJsString(content)
|
||||
webViewRefForTts?.evaluateJavascript(
|
||||
"javascript:window.virtualization.appendChunk($idx, '$escaped');",
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
loadUpToChunkIndex = targetChunk
|
||||
loadedChunkCount = max(loadedChunkCount, targetChunk + 1)
|
||||
}
|
||||
searchHighlightTarget = result
|
||||
},
|
||||
onPaginatedScrollToPage = { pageIdx ->
|
||||
Timber.tag("NavDiag").d("onPaginatedScrollToPage pageIdx=$pageIdx")
|
||||
paginatedPagerState.scrollToPage(pageIdx)
|
||||
}
|
||||
)
|
||||
|
|
@ -1763,23 +1797,8 @@ fun EpubReaderHost(
|
|||
Timber.tag("BookmarkDiagnosis").d("Navigating to ${bookmark.cfi}")
|
||||
cfiToLoad = bookmark.cfi
|
||||
|
||||
val directChunkIndex = try {
|
||||
val parts = bookmark.cfi.split('/').mapNotNull { it.toIntOrNull() }
|
||||
if (parts.isNotEmpty()) {
|
||||
val firstIndex = parts[0]
|
||||
(firstIndex - 2) / 2
|
||||
} else null
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
val locator = if (directChunkIndex == null) {
|
||||
locatorConverter.getLocatorFromCfi(epubBook, bookmark.chapterIndex, bookmark.cfi)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val targetChunk = directChunkIndex ?: locator?.let { it.blockIndex / 20 }
|
||||
val locator = locatorConverter.getLocatorFromCfi(epubBook, bookmark.chapterIndex, bookmark.cfi)
|
||||
val targetChunk = locator?.let { it.blockIndex / 20 }
|
||||
|
||||
if (bookmark.chapterIndex != currentChapterIndex) {
|
||||
chunkTargetOverride = if (targetChunk != null && targetChunk >= 0) {
|
||||
|
|
@ -2168,7 +2187,7 @@ fun EpubReaderHost(
|
|||
} else if (chapterChunks.isNotEmpty()) {
|
||||
val initialContentToLoad = remember(loadUpToChunkIndex, chapterChunks) {
|
||||
val targetIdx = loadUpToChunkIndex
|
||||
val startIdx = maxOf(0, targetIdx - 1)
|
||||
val startIdx = 0
|
||||
val endIdx = minOf(chapterChunks.lastIndex, targetIdx + 1)
|
||||
|
||||
chapterChunks.indices.joinToString(separator = "\n") { index ->
|
||||
|
|
@ -2221,30 +2240,31 @@ fun EpubReaderHost(
|
|||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(isWebViewReady) {
|
||||
LaunchedEffect(isWebViewReady, searchHighlightTarget) {
|
||||
val target = searchHighlightTarget
|
||||
Timber.d("Effect(isWebViewReady=$isWebViewReady) triggered for chapter $targetChapterIndex. Target is: $target"
|
||||
)
|
||||
Timber.tag("NavDiag").d("Effect(isWebViewReady=$isWebViewReady, target=$target) triggered for chapter $targetChapterIndex.")
|
||||
|
||||
if (isWebViewReady && target != null && target.locationInSource == targetChapterIndex) {
|
||||
Timber.d("Highlighting condition met. Highlighting now."
|
||||
)
|
||||
Timber.tag("NavDiag").d("Highlighting condition met. Highlighting now.")
|
||||
delay(200)
|
||||
val webView = webViewRefForTts
|
||||
if (webView != null) {
|
||||
val escapedQuery = escapeJsString(target.query)
|
||||
val js =
|
||||
"javascript:window.highlightAllOccurrences('${escapedQuery}'); window.scrollToOccurrence(${target.occurrenceIndexInLocation});"
|
||||
Timber.d("Executing search highlight/scroll JS: $js"
|
||||
)
|
||||
val targetChunk = target.chunkIndex
|
||||
|
||||
val relativeIdx = searchState.searchResults
|
||||
.filter { it.locationInSource == target.locationInSource && it.chunkIndex == targetChunk }
|
||||
.indexOf(target)
|
||||
.coerceAtLeast(0)
|
||||
|
||||
val js = "javascript:console.log('NavDiag: Executing robust search highlight JS'); window.CURRENT_SEARCH_QUERY = '${escapedQuery}'; window.highlightAllOccurrences('${escapedQuery}'); window.scrollToChunkOccurrence($targetChunk, $relativeIdx);"
|
||||
Timber.tag("NavDiag").d("Executing search highlight/scroll JS: $js")
|
||||
webView.evaluateJavascript(js) { result ->
|
||||
Timber.d("JS highlight/scroll result: $result"
|
||||
)
|
||||
Timber.tag("NavDiag").d("JS highlight/scroll result: $result")
|
||||
}
|
||||
searchHighlightTarget = null
|
||||
} else {
|
||||
Timber.w("Highlight failed: WebView was null even after ready signal."
|
||||
)
|
||||
Timber.tag("NavDiag").w("Highlight failed: WebView was null even after ready signal.")
|
||||
searchHighlightTarget = null
|
||||
}
|
||||
}
|
||||
|
|
@ -2305,6 +2325,7 @@ fun EpubReaderHost(
|
|||
},
|
||||
onChapterInitiallyScrolled = {
|
||||
val wasCfiScroll = cfiToLoad != null
|
||||
Timber.tag("NavDiag").d("onChapterInitiallyScrolled for chapter $targetChapterIndex. Was CFI scroll: $wasCfiScroll")
|
||||
initialScrollTargetForChapter = null
|
||||
cfiToLoad = null
|
||||
fragmentToLoad = null
|
||||
|
|
@ -3485,6 +3506,8 @@ fun EpubReaderHost(
|
|||
tapToNavigateEnabled = tapToNavigateEnabled,
|
||||
volumeScrollEnabled = volumeScrollEnabled,
|
||||
isPageTurnAnimationEnabled = isPageTurnAnimationEnabled,
|
||||
hiddenTools = hiddenTools,
|
||||
onCustomizeTools = { showCustomizeToolsSheet = true },
|
||||
onNavigateBack = { triggerSaveAndExit() },
|
||||
isKeepScreenOn = isKeepScreenOn,
|
||||
onToggleKeepScreenOn = { enabled ->
|
||||
|
|
@ -3497,23 +3520,34 @@ fun EpubReaderHost(
|
|||
keyboardController?.hide()
|
||||
focusManager.clearFocus()
|
||||
containerFocusRequester.requestFocus()
|
||||
webViewRefForTts?.evaluateJavascript("javascript:window.clearSearchHighlights();", null)
|
||||
},
|
||||
onChangeRenderMode = { newMode ->
|
||||
Timber.tag("NavDiag").d("onChangeRenderMode to $newMode")
|
||||
if (newMode != currentRenderMode) {
|
||||
if (newMode == RenderMode.PAGINATED) {
|
||||
isSwitchingToPaginated = true
|
||||
webViewRefForTts?.evaluateJavascript("javascript:CfiBridge.onCfiExtracted(window.getCurrentCfi());", null)
|
||||
} else {
|
||||
scope.launch {
|
||||
Timber.tag("NavDiag").d("Mode changing to VERTICAL. lastKnownLocator=$lastKnownLocator")
|
||||
lastKnownLocator?.let { locator ->
|
||||
val cfi = locatorConverter.getCfiFromLocator(epubBook, locator)
|
||||
Timber.tag("NavDiag").d("Converted locator to CFI: $cfi")
|
||||
if (cfi != null) {
|
||||
val targetChunk = locator.blockIndex / 20
|
||||
chunkTargetOverride = targetChunk
|
||||
if (currentChapterIndex != locator.chapterIndex) {
|
||||
initialScrollTargetForChapter = null
|
||||
currentScrollYPosition = 0
|
||||
currentScrollHeightValue = 0
|
||||
currentChapterIndex = locator.chapterIndex
|
||||
} else {
|
||||
if (targetChunk > loadUpToChunkIndex) {
|
||||
loadUpToChunkIndex = targetChunk
|
||||
loadedChunkCount = max(loadedChunkCount, targetChunk + 1)
|
||||
}
|
||||
initialScrollTargetForChapter = null
|
||||
}
|
||||
cfiToLoad = cfi
|
||||
} else {
|
||||
|
|
@ -3661,7 +3695,15 @@ fun EpubReaderHost(
|
|||
saveAutoScrollUseSlider(context, autoScrollUseSlider)
|
||||
},
|
||||
isLocalMode = isAutoScrollLocal,
|
||||
onLocalModeToggle = onToggleAutoScrollMode
|
||||
onLocalModeToggle = onToggleAutoScrollMode,
|
||||
onScrollToTop = {
|
||||
if (isAutoScrollPlaying) {
|
||||
triggerAutoScrollTempPause(1000L)
|
||||
}
|
||||
scope.launch {
|
||||
webViewRefForTts?.evaluateJavascript("window.scrollTo({ top: 0, behavior: 'smooth' });", null)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -3672,6 +3714,7 @@ fun EpubReaderHost(
|
|||
isTtsSessionActive = isTtsSessionActive,
|
||||
ttsState = ttsState,
|
||||
isProUser = isProUser,
|
||||
hiddenTools = hiddenTools,
|
||||
onOpenSlider = {
|
||||
when (currentRenderMode) {
|
||||
RenderMode.VERTICAL_SCROLL -> {
|
||||
|
|
@ -4123,6 +4166,17 @@ fun EpubReaderHost(
|
|||
)
|
||||
}
|
||||
|
||||
if (showCustomizeToolsSheet) {
|
||||
CustomizeToolsSheet(
|
||||
hiddenTools = hiddenTools,
|
||||
onUpdate = { newHiddenSet ->
|
||||
hiddenTools = newHiddenSet
|
||||
saveHiddenTools(context, newHiddenSet)
|
||||
},
|
||||
onDismiss = { showCustomizeToolsSheet = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showDictionarySettingsSheet) {
|
||||
DictionarySettingsDialog(
|
||||
isVisible = true,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue