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:
parent
15264a31ae
commit
6fd6dd5609
20 changed files with 623 additions and 134 deletions
|
|
@ -82,6 +82,7 @@ import androidx.compose.material.icons.filled.PlayArrow
|
|||
import androidx.compose.material.icons.filled.Remove
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.SwapHoriz
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
|
|
@ -155,6 +156,7 @@ fun EpubReaderTopBar(
|
|||
onOpenDeviceVoiceSettings: () -> Unit,
|
||||
onOpenDictionarySettings: () -> Unit,
|
||||
onOpenThemeSettings: () -> Unit,
|
||||
onOpenVisualOptions: () -> Unit,
|
||||
searchFocusRequester: androidx.compose.ui.focus.FocusRequester,
|
||||
modifier: Modifier = Modifier,
|
||||
onToggleReflow: (() -> Unit)? = null,
|
||||
|
|
@ -346,6 +348,18 @@ fun EpubReaderTopBar(
|
|||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text("Visual Options") },
|
||||
onClick = {
|
||||
showMoreMenu = false
|
||||
onOpenVisualOptions()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(Icons.Default.Visibility, contentDescription = null, modifier = Modifier.size(20.dp))
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text("Auto Scroll") },
|
||||
enabled = !isTtsActive && currentRenderMode == RenderMode.VERTICAL_SCROLL,
|
||||
|
|
@ -357,7 +371,6 @@ fun EpubReaderTopBar(
|
|||
|
||||
HorizontalDivider()
|
||||
|
||||
// *** ADDITION START ***
|
||||
DropdownMenuItem(
|
||||
text = { Text("TTS Voice Settings") },
|
||||
onClick = {
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -91,6 +91,12 @@ import androidx.core.content.edit
|
|||
import com.aryan.reader.R
|
||||
import com.aryan.reader.data.CustomFontEntity
|
||||
import java.io.File
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
|
||||
const val SETTINGS_PREFS_NAME = "epub_reader_settings"
|
||||
private const val TEXT_ALIGN_KEY = "reader_text_align"
|
||||
|
|
@ -100,6 +106,9 @@ private const val AUTO_SCROLL_SPEED_KEY = "reader_auto_scroll_speed"
|
|||
private const val FONT_FAMILY_KEY = "reader_font_family"
|
||||
private const val TAP_TO_NAVIGATE_ENABLED_KEY = "tap_to_navigate_enabled"
|
||||
private const val VOLUME_SCROLL_ENABLED_KEY = "volume_scroll_enabled"
|
||||
private const val SYSTEM_UI_MODE_KEY = "reader_system_ui_mode"
|
||||
private const val PAGE_INFO_MODE_KEY = "reader_page_info_mode"
|
||||
private const val PULL_TO_TURN_ENABLED_KEY = "reader_pull_to_turn_enabled"
|
||||
|
||||
const val DEFAULT_FONT_SIZE_VAL = 1.0f
|
||||
const val DEFAULT_LINE_HEIGHT_VAL = 1.6f
|
||||
|
|
@ -119,6 +128,18 @@ enum class ReaderTextAlign(val id: String, val cssValue: String, val iconResId:
|
|||
JUSTIFY("justify", "justify", R.drawable.format_align_justify, "Justify")
|
||||
}
|
||||
|
||||
enum class SystemUiMode(val id: Int, val title: String) {
|
||||
DEFAULT(0, "Always Show"),
|
||||
SYNC(1, "Sync with Menus"),
|
||||
HIDDEN(2, "Always Hide")
|
||||
}
|
||||
|
||||
enum class PageInfoMode(val id: Int, val title: String) {
|
||||
DEFAULT(0, "Always Show"),
|
||||
SYNC(1, "Sync with Menus"),
|
||||
HIDDEN(2, "Always Hide")
|
||||
}
|
||||
|
||||
data class FormatSettings(
|
||||
val fontSize: Float,
|
||||
val lineHeight: Float,
|
||||
|
|
@ -165,6 +186,38 @@ fun saveLocalReaderSettings(
|
|||
}
|
||||
}
|
||||
|
||||
fun saveSystemUiMode(context: Context, mode: SystemUiMode) {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit { putInt(SYSTEM_UI_MODE_KEY, mode.id) }
|
||||
}
|
||||
|
||||
fun loadSystemUiMode(context: Context): SystemUiMode {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
val id = prefs.getInt(SYSTEM_UI_MODE_KEY, SystemUiMode.DEFAULT.id)
|
||||
return SystemUiMode.entries.find { it.id == id } ?: SystemUiMode.DEFAULT
|
||||
}
|
||||
|
||||
fun savePageInfoMode(context: Context, mode: PageInfoMode) {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit { putInt(PAGE_INFO_MODE_KEY, mode.id) }
|
||||
}
|
||||
|
||||
fun loadPageInfoMode(context: Context): PageInfoMode {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
val id = prefs.getInt(PAGE_INFO_MODE_KEY, PageInfoMode.DEFAULT.id)
|
||||
return PageInfoMode.entries.find { it.id == id } ?: PageInfoMode.DEFAULT
|
||||
}
|
||||
|
||||
fun savePullToTurn(context: Context, enabled: Boolean) {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit { putBoolean(PULL_TO_TURN_ENABLED_KEY, enabled) }
|
||||
}
|
||||
|
||||
fun loadPullToTurn(context: Context): Boolean {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
return prefs.getBoolean(PULL_TO_TURN_ENABLED_KEY, true)
|
||||
}
|
||||
|
||||
fun loadFormatSettings(context: Context, bookId: String, isLocal: Boolean): FormatSettings {
|
||||
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
|
|
@ -591,4 +644,128 @@ fun FontSelectionSheetContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun VisualOptionsSheet(
|
||||
systemUiMode: SystemUiMode,
|
||||
onSystemUiModeChange: (SystemUiMode) -> Unit,
|
||||
pageInfoMode: PageInfoMode,
|
||||
onPageInfoModeChange: (PageInfoMode) -> Unit,
|
||||
pullToTurnEnabled: Boolean,
|
||||
onPullToTurnChange: (Boolean) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentWindowInsets = { WindowInsets.navigationBars }
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("Visual Options", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(Icons.Default.Close, contentDescription = "Close")
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// System UI
|
||||
Text("System UI (Status & Navigation Bars)", style = MaterialTheme.typography.titleMedium)
|
||||
Text("Control the visibility of the device's system bars.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
OptionSegmentedControl(
|
||||
options = SystemUiMode.entries,
|
||||
selectedOption = systemUiMode,
|
||||
onOptionSelected = onSystemUiModeChange,
|
||||
getLabel = { it.title }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Progress Bar
|
||||
Text("Progress Bar", style = MaterialTheme.typography.titleMedium)
|
||||
Text("The reading progress and chapter indicator at the bottom of the screen.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
OptionSegmentedControl(
|
||||
options = PageInfoMode.entries,
|
||||
selectedOption = pageInfoMode,
|
||||
onOptionSelected = onPageInfoModeChange,
|
||||
getLabel = { it.title }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Pull to change chapter
|
||||
Surface(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onPullToTurnChange(!pullToTurnEnabled) }
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text("Seamless Chapter Transition", style = MaterialTheme.typography.titleMedium)
|
||||
Text("Instantly load the next/previous chapter when scrolling past the end, without the pull-to-refresh animation.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Switch(checked = !pullToTurnEnabled, onCheckedChange = { onPullToTurnChange(!it) })
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> OptionSegmentedControl(
|
||||
options: List<T>,
|
||||
selectedOption: T,
|
||||
onOptionSelected: (T) -> Unit,
|
||||
getLabel: (T) -> String
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), RoundedCornerShape(12.dp))
|
||||
.padding(4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
options.forEach { option ->
|
||||
val isSelected = option == selectedOption
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent)
|
||||
.clickable { onOptionSelected(option) }
|
||||
.padding(vertical = 10.dp, horizontal = 4.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = getLabel(option),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,8 @@ fun EpubReaderSystemUiController(
|
|||
showBars: Boolean,
|
||||
initialIsAppearanceLightStatusBars: Boolean,
|
||||
initialSystemBarsBehavior: Int,
|
||||
isDarkTheme: Boolean
|
||||
isDarkTheme: Boolean,
|
||||
systemUiMode: SystemUiMode
|
||||
) {
|
||||
DisposableEffect(window, view, initialIsAppearanceLightStatusBars, initialSystemBarsBehavior) {
|
||||
if (window == null) {
|
||||
|
|
@ -53,13 +54,12 @@ fun EpubReaderSystemUiController(
|
|||
Timber.d("Applying immersive mode.")
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
insetsController.hide(WindowInsetsCompat.Type.navigationBars())
|
||||
insetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
|
||||
onDispose {
|
||||
Timber.d("Restoring system UI.")
|
||||
WindowCompat.setDecorFitsSystemWindows(window, true)
|
||||
insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
||||
insetsController.show(WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.statusBars())
|
||||
insetsController.isAppearanceLightStatusBars = initialIsAppearanceLightStatusBars
|
||||
insetsController.systemBarsBehavior = initialSystemBarsBehavior
|
||||
}
|
||||
|
|
@ -72,13 +72,25 @@ fun EpubReaderSystemUiController(
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(showBars, window, view) {
|
||||
LaunchedEffect(showBars, systemUiMode, window, view) {
|
||||
if (window != null) {
|
||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||
if (showBars) {
|
||||
insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
||||
} else {
|
||||
insetsController.hide(WindowInsetsCompat.Type.navigationBars())
|
||||
when (systemUiMode) {
|
||||
SystemUiMode.DEFAULT -> {
|
||||
insetsController.show(WindowInsetsCompat.Type.statusBars())
|
||||
if (showBars) insetsController.show(WindowInsetsCompat.Type.navigationBars())
|
||||
else insetsController.hide(WindowInsetsCompat.Type.navigationBars())
|
||||
}
|
||||
SystemUiMode.SYNC -> {
|
||||
if (showBars) {
|
||||
insetsController.show(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||
} else {
|
||||
insetsController.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||
}
|
||||
}
|
||||
SystemUiMode.HIDDEN -> {
|
||||
insetsController.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue