Desktop app (#308)
* Implement build profiles and feature policy for offline desktop builds * Introduce unified cross-platform Settings Hub * Refactor main settings into a hierarchical page-based navigation model * Refactor library projection to use shared multiplatform logic * Refactor UI state consumption by removing intermediate screen models * Introduce AndroidSharedStateBridge to centralize state mapping and reduction logic * Refactor state management for tabs, selection, and pinning to use shared bridge logic * Refactor file type management and validation into a centralized shared module * Centralize file type resolution and improve handling of unknown types * Centralize book import logic with SharedImportPlanner * Refactor magnifier geometry logic and coordinate mapping * Properly handle orientation changes in scroll-locked PDF reader * Add screen orientation controls to EPUB and PDF readers * Implement right-to-left (RTL) pagination support and refactor reader menus * Separate right-to-left pagination settings for PDF and EPUB * Ensure PDF page data is scoped by document key for multi tab support * Implement theme-aware link styling for the epub reader * Implement jump history for back and forward navigation in the epub reader * Improve locator handling and navigation logic in paginated reader mode * Implement stable pagination navigation and location tracking * Centralize banner message management and auto-dismiss logic in MainViewModel * Implement zoom and pan state preservation for PDF pan lock mode * Enhance reader navigation UI and workspace layout management in desktop app * Refactor reader navigation sidebar and relocate search controls in desktop app * Enhance reader UI with redesigned selection menus and bottom sheet overlays * Implement custom highlight palettes and reader theme customization in desktop app * Implement cross-platform modal layer and refine reader UI styling * Improve highlight accuracy and implement metadata enrichment on book open in desktop app * Implement two-page spread layout for paginated reader on desktop * Implement persistent caching for book loading and pagination in desktop app * Implement persistent caching for book loading and pagination in desktop app * Optimize reader settings updates by separating layout and appearance changes in desktop app * Improve desktop window branding and native Windows styling * Enhance reader selection interactions and UI across EPUB and PDF viewers in desktop app * Refine selection handle positioning and interaction logic * Implement EPUB selection debug logging and improve handle targeting * Optimize desktop book loading performance and UI responsiveness * Implement anchored zoom gestures and rendering optimizations for the Desktop PDF viewer. * Implement smooth zoom preview for the PDF reader in desktop app * Optimize PDF rendering performance and responsiveness in the desktop reader * Implement conditional diagnostic logging and update desktop build configuration * Implemented hierarchical TOC, custom scrollbars, and improved desktop modal handling * Added management options for annotations and highlights in the sidebar in desktop app * Implemented `SharedStableOutlinedTextField` and updated text input fields to use `TextFieldValue` for improved cursor and selection stability. * Refined library filters and enhanced OPDS functionality in desktop app * Improved EPUB pagination measurement and implemented layout diagnostic logging for desktop app * Added PPTX support including document parsing, rendering, and indexing * Improved PPTX rendering and layout accuracy * Implemented text autofit support for PPTX rendering * Enhanced PPTX rendering with support for custom geometry, automatic numbering, table styles, and image opacity * Improved EPUB pagination accuracy and added layout telemetry in desktop app * Improved folder synchronization with metadata-only mode and hashed sidecar management in desktop app * Implemented rich text font scaling and migrated desktop ink tools to custom pointer input handling * Implemented billing account obfuscation * Implemented hierarchical folder navigation and improved library selection functionality in desktop app * Implemented platform-aware directory resolution and multi-platform native library support for desktop * Added full-screen mode for the reader workspace * Added PDF zoom indicator and interactive vertical scrollbar with page tooltips * Refactored speech bubble prefetching to use a limited radius and improved ML detector initialization and lifecycle management * Updated PDF indexing to replace existing page text and removed search result item keys * Implemented "preparing" foreground notification for TTS service * Optimized PDF rendering performance by pre-calculating page-specific annotations * Refactored desktop packaging tasks and improved distribution configuration * Optimized EPUB parser memory usage and added path traversal protection * Refactored WorkManager monitoring logic and added work pruning * Implemented comprehensive resource cleanup and memory management for WebView-based components to prevent memory leaks * Implemented bitmap size limits and scaling to prevent canvas rendering errors * Split long text paragraphs into multiple semantic blocks during HTML parsing * Implemented local ActionMode for text selection to prevent platform crashes * Refactored PPTX text layout, optimized HtmlParser block detection, and improved banner dismissal logic * Added desktop startup splash screen and deferred WebView initialization * Reorganized settings hub and added separate PDF reader defaults * Implemented embedded cover extraction and metadata support for MOBI and FB2 formats * Implemented batching for MetadataExtractionWorker and optimized EPUB metadata extraction performance. * Implemented procedurally generated book covers and replaced static placeholders * Redesigned search UI with a top bar and results overlay in desktop app * Added PDF page gap and overlay visibility options and implemented DesktopBookImporter * Refactored PDF reader UI with tabbed inspector and improved theme background handling in desktop * Implemented PDF viewport persistence for zoom and scroll positions in desktop app * Improved desktop fullscreen implementation and state restoration * Implemented desktop window state persistence * Implemented flavor-based branding and ProGuard configuration for desktop builds * Implemented precise reader positioning and improved highlight rendering logic in desktop app * Added support for user-editable book metadata * Enhanced book metadata support and integrated info/edit dialogs * Implemented embedded EPUB metadata editing * Improved highlight mapping and added custom scrollbar styling for the reader. * Reduced desktop WebView bundle size by excluding unused locales and runtime files * Added neutral pan mode as the default PDF interaction state. * Refactored library empty states and updated primary navigation tabs in desktop app * Implemented native paginated reader and unified content rendering architecture in desktop epub reader * Implemented native EPUB image rendering for desktop and improved block layout spacing with margin collapsing. * Improved pagination overflow detection in desktop * Implemented multi-block text selection with interactive handles and CFI support in desktop epub pagination
This commit is contained in:
parent
c0d0e57e79
commit
b20ade9946
247 changed files with 43321 additions and 7087 deletions
583
app/src/main/java/com/aryan/reader/SettingsScreen.kt
Normal file
583
app/src/main/java/com/aryan/reader/SettingsScreen.kt
Normal file
|
|
@ -0,0 +1,583 @@
|
|||
package com.aryan.reader
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.navigation.NavHostController
|
||||
import com.aryan.reader.data.CustomFontEntity
|
||||
import com.aryan.reader.epubreader.FormatSettings as AndroidFormatSettings
|
||||
import com.aryan.reader.epubreader.PageInfoMode as AndroidPageInfoMode
|
||||
import com.aryan.reader.epubreader.PageInfoPosition as AndroidPageInfoPosition
|
||||
import com.aryan.reader.epubreader.ReaderFont as AndroidReaderFont
|
||||
import com.aryan.reader.epubreader.ReaderTextAlign as AndroidReaderTextAlign
|
||||
import com.aryan.reader.epubreader.SystemUiMode as AndroidSystemUiMode
|
||||
import com.aryan.reader.epubreader.loadFormatSettings
|
||||
import com.aryan.reader.epubreader.loadPageInfoMode
|
||||
import com.aryan.reader.epubreader.loadPageInfoPosition
|
||||
import com.aryan.reader.epubreader.loadPullToTurn
|
||||
import com.aryan.reader.epubreader.loadPullToTurnMultiplier
|
||||
import com.aryan.reader.epubreader.loadSystemUiMode
|
||||
import com.aryan.reader.epubreader.savePageInfoMode
|
||||
import com.aryan.reader.epubreader.savePageInfoPosition
|
||||
import com.aryan.reader.epubreader.savePullToTurn
|
||||
import com.aryan.reader.epubreader.savePullToTurnMultiplier
|
||||
import com.aryan.reader.epubreader.saveReaderSettings
|
||||
import com.aryan.reader.epubreader.saveSystemUiMode
|
||||
import com.aryan.reader.pdf.savePdfSystemUiMode
|
||||
import com.aryan.reader.pdf.savePdfThemeId
|
||||
import com.aryan.reader.pdf.savePdfVerticalPageGapVisible
|
||||
import com.aryan.reader.pdf.savePdfPageNumberOverlayVisible
|
||||
import com.aryan.reader.pdf.loadPdfSystemUiMode
|
||||
import com.aryan.reader.pdf.loadPdfThemeId
|
||||
import com.aryan.reader.pdf.loadPdfVerticalPageGapVisible
|
||||
import com.aryan.reader.pdf.loadPdfPageNumberOverlayVisible
|
||||
import com.aryan.reader.shared.BuiltInPdfReaderThemes
|
||||
import com.aryan.reader.shared.CustomFontItem
|
||||
import com.aryan.reader.shared.PageInfoMode as SharedPageInfoMode
|
||||
import com.aryan.reader.shared.PageInfoPosition as SharedPageInfoPosition
|
||||
import com.aryan.reader.shared.SharedSettingsAction
|
||||
import com.aryan.reader.shared.SharedSettingsDestination
|
||||
import com.aryan.reader.shared.SystemUiMode as SharedSystemUiMode
|
||||
import com.aryan.reader.shared.parentDestination
|
||||
import com.aryan.reader.shared.reader.ReaderReadingMode
|
||||
import com.aryan.reader.shared.reader.ReaderSettings
|
||||
import com.aryan.reader.shared.reader.SharedReaderTextAlign
|
||||
import com.aryan.reader.shared.readerThemeById
|
||||
import com.aryan.reader.shared.sharedSettingsHubModel
|
||||
import com.aryan.reader.shared.toReaderSettings
|
||||
import com.aryan.reader.shared.ui.SharedSettingsHub
|
||||
import com.aryan.reader.tts.loadTtsMode
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val ANDROID_SETTINGS_GLOBAL_BOOK_ID = "__global_reader_defaults__"
|
||||
|
||||
@androidx.annotation.OptIn(UnstableApi::class)
|
||||
@kotlin.OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: MainViewModel,
|
||||
navController: NavHostController,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val customFonts by viewModel.customFonts.collectAsStateWithLifecycle()
|
||||
val ttsState by viewModel.ttsController.ttsState.collectAsStateWithLifecycle()
|
||||
|
||||
var query by remember { mutableStateOf("") }
|
||||
var settingsDestination by remember { mutableStateOf(SharedSettingsDestination.ROOT) }
|
||||
var showAppThemePanel by remember { mutableStateOf(false) }
|
||||
var showBehaviorDialog by remember { mutableStateOf(false) }
|
||||
var showStrictFilterDialog by remember { mutableStateOf(false) }
|
||||
var showClearBookCacheDialog by remember { mutableStateOf(false) }
|
||||
var showClearReflowCacheDialog by remember { mutableStateOf(false) }
|
||||
var showClearAllDataDialog by remember { mutableStateOf(false) }
|
||||
var showLanguageDialog by remember { mutableStateOf(false) }
|
||||
var showAboutDialog by remember { mutableStateOf(false) }
|
||||
var showSignOutConfirmDialog by remember { mutableStateOf(false) }
|
||||
var showUpgradeDialog by remember { mutableStateOf(false) }
|
||||
var showRecentLimitDialog by remember { mutableStateOf(false) }
|
||||
var showTtsSettingsSheet by remember { mutableStateOf(false) }
|
||||
var hideReaderAi by remember { mutableStateOf(loadHideReaderAiFeatures(context)) }
|
||||
var epubReaderDefaults by remember(context, uiState.renderMode) {
|
||||
mutableStateOf(loadAndroidEpubReaderDefaultSettings(context, uiState.renderMode))
|
||||
}
|
||||
var pdfReaderDefaults by remember(context) {
|
||||
mutableStateOf(loadAndroidPdfReaderDefaultSettings(context))
|
||||
}
|
||||
var ttsReplacementPreferences by remember(context) {
|
||||
mutableStateOf(loadTtsReplacementPreferences(context))
|
||||
}
|
||||
var ttsMode by remember(context) { mutableStateOf(loadTtsMode(context)) }
|
||||
|
||||
LaunchedEffect(uiState.renderMode) {
|
||||
epubReaderDefaults = loadAndroidEpubReaderDefaultSettings(context, uiState.renderMode)
|
||||
}
|
||||
|
||||
val sharedFonts = remember(customFonts) {
|
||||
customFonts.toSharedCustomFontItems()
|
||||
}
|
||||
|
||||
val settingsModel = sharedSettingsHubModel(
|
||||
androidSettingsHubInput(
|
||||
uiState = uiState,
|
||||
hideReaderAi = hideReaderAi
|
||||
)
|
||||
)
|
||||
val settingsPage = settingsModel.page(settingsDestination)
|
||||
|
||||
fun navigateBackFromSettings() {
|
||||
if (query.isNotBlank()) {
|
||||
query = ""
|
||||
return
|
||||
}
|
||||
val parent = settingsDestination.parentDestination()
|
||||
if (parent != null) {
|
||||
settingsDestination = parent
|
||||
} else {
|
||||
onBackClick()
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = query.isNotBlank() || settingsDestination != SharedSettingsDestination.ROOT) {
|
||||
navigateBackFromSettings()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
CustomTopAppBar(
|
||||
title = { Text(settingsPage.title) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = ::navigateBackFromSettings) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
contentWindowInsets = WindowInsets.navigationBars
|
||||
) { padding ->
|
||||
SharedSettingsHub(
|
||||
model = settingsModel,
|
||||
query = query,
|
||||
onQueryChange = { query = it },
|
||||
readerDefaultSettings = epubReaderDefaults,
|
||||
onReaderDefaultSettingsChange = { settings ->
|
||||
epubReaderDefaults = settings
|
||||
saveAndroidEpubReaderDefaultSettings(context, settings)
|
||||
viewModel.setRenderMode(settings.toAndroidRenderMode())
|
||||
},
|
||||
pdfReaderDefaultSettings = pdfReaderDefaults,
|
||||
onPdfReaderDefaultSettingsChange = { settings ->
|
||||
pdfReaderDefaults = settings
|
||||
saveAndroidPdfReaderDefaultSettings(context, settings)
|
||||
},
|
||||
ttsReplacementPreferences = ttsReplacementPreferences,
|
||||
onTtsReplacementPreferencesChange = { preferences ->
|
||||
ttsReplacementPreferences = preferences
|
||||
saveTtsReplacementPreferences(context, preferences)
|
||||
},
|
||||
customFonts = sharedFonts,
|
||||
showTopBar = false,
|
||||
destination = settingsDestination,
|
||||
onDestinationChange = { settingsDestination = it },
|
||||
contentPadding = padding,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
SharedSettingsAction.APP_THEME -> showAppThemePanel = true
|
||||
SharedSettingsAction.LANGUAGE -> showLanguageDialog = true
|
||||
SharedSettingsAction.TABS_TOGGLE -> viewModel.setTabsEnabled(!uiState.isTabsEnabled)
|
||||
SharedSettingsAction.RECENT_LIMIT -> showRecentLimitDialog = true
|
||||
SharedSettingsAction.STRICT_FILE_FILTER -> {
|
||||
if (uiState.useStrictFileFilter) {
|
||||
viewModel.setStrictFileFilter(false)
|
||||
} else {
|
||||
showStrictFilterDialog = true
|
||||
}
|
||||
}
|
||||
SharedSettingsAction.EXTERNAL_FILE_BEHAVIOR -> showBehaviorDialog = true
|
||||
SharedSettingsAction.SCREEN_CAPTURE_PROTECTION -> {
|
||||
val next = !uiState.isScreenCaptureProtectionEnabled
|
||||
viewModel.setScreenCaptureProtectionEnabled(next)
|
||||
val messageRes = if (next) {
|
||||
R.string.banner_screen_capture_protection_on
|
||||
} else {
|
||||
R.string.banner_screen_capture_protection_off
|
||||
}
|
||||
viewModel.showBanner(context.getString(messageRes))
|
||||
}
|
||||
SharedSettingsAction.CUSTOM_FONTS -> navController.navigate(AppDestinations.FONTS_SCREEN_ROUTE)
|
||||
SharedSettingsAction.SIGN_IN -> {
|
||||
scope.launch {
|
||||
context.findActivity()?.let { activity -> viewModel.signIn(activity) }
|
||||
}
|
||||
}
|
||||
SharedSettingsAction.SIGN_OUT -> showSignOutConfirmDialog = true
|
||||
SharedSettingsAction.CLOUD_SYNC -> {
|
||||
if (uiState.isProUser) {
|
||||
viewModel.setSyncEnabled(!uiState.isSyncEnabled)
|
||||
} else {
|
||||
showUpgradeDialog = true
|
||||
}
|
||||
}
|
||||
SharedSettingsAction.FOLDER_SYNC -> viewModel.setFolderSyncEnabled(!uiState.isFolderSyncEnabled)
|
||||
SharedSettingsAction.DEVICE_MANAGEMENT -> viewModel.showDeviceManagementForDebug()
|
||||
SharedSettingsAction.AI_SETTINGS -> navController.navigate(AppDestinations.AI_SETTINGS_SCREEN_ROUTE)
|
||||
SharedSettingsAction.HIDE_READER_AI -> {
|
||||
val nextHidden = !hideReaderAi
|
||||
saveHideReaderAiFeatures(context, nextHidden)
|
||||
hideReaderAi = nextHidden
|
||||
}
|
||||
SharedSettingsAction.TTS_SETTINGS -> showTtsSettingsSheet = true
|
||||
SharedSettingsAction.CLEAR_BOOK_CACHE -> showClearBookCacheDialog = true
|
||||
SharedSettingsAction.CLEAR_REFLOW_CACHE -> showClearReflowCacheDialog = true
|
||||
SharedSettingsAction.CLEAR_CLOUD_LOCAL_DATA -> showClearAllDataDialog = true
|
||||
SharedSettingsAction.TEST_PANEL_DETECTION -> viewModel.testPanelDetection(context)
|
||||
SharedSettingsAction.TEST_SPEECH_BUBBLE_DETECTION -> viewModel.testSpeechBubbleDetection(context)
|
||||
SharedSettingsAction.EXPORT_LOGS -> viewModel.exportLogsToFile(context)
|
||||
SharedSettingsAction.DEBUG_ACTIONS -> viewModel.showBanner("Debug actions remain in their existing menus.")
|
||||
SharedSettingsAction.HELP_FEEDBACK -> navController.navigate(AppDestinations.FEEDBACK_SCREEN_ROUTE)
|
||||
SharedSettingsAction.SUPPORT -> navController.navigate(AppDestinations.SUPPORT_PROJECT_SCREEN_ROUTE)
|
||||
SharedSettingsAction.ABOUT -> showAboutDialog = true
|
||||
SharedSettingsAction.PDF_READER_DEFAULTS -> viewModel.showBanner("PDF-specific OCR, annotation, and tool settings remain in the PDF reader.")
|
||||
SharedSettingsAction.TEXT_READER_DEFAULTS,
|
||||
SharedSettingsAction.READER_TOOLBAR,
|
||||
SharedSettingsAction.TTS_REPLACEMENTS,
|
||||
SharedSettingsAction.LOCAL_OVERRIDE_NOTE -> Unit
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showRecentLimitDialog) {
|
||||
RecentLimitDialog(
|
||||
currentLimit = uiState.recentFilesLimit,
|
||||
onSelect = { limit ->
|
||||
viewModel.setRecentFilesLimit(limit)
|
||||
showRecentLimitDialog = false
|
||||
},
|
||||
onDismiss = { showRecentLimitDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showBehaviorDialog) {
|
||||
ExternalFileBehaviorDialog(
|
||||
currentBehavior = uiState.externalFileBehavior,
|
||||
onDismiss = { showBehaviorDialog = false },
|
||||
onSelect = { viewModel.setExternalFileBehavior(it) }
|
||||
)
|
||||
}
|
||||
|
||||
if (showStrictFilterDialog) {
|
||||
StrictFilterConfirmationDialog(
|
||||
onConfirm = {
|
||||
viewModel.setStrictFileFilter(true)
|
||||
showStrictFilterDialog = false
|
||||
},
|
||||
onDismiss = { showStrictFilterDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showClearBookCacheDialog) {
|
||||
DangerousFolderActionDialog(
|
||||
title = context.getString(R.string.dialog_clear_book_cache),
|
||||
message = context.getString(R.string.dialog_clear_book_cache_desc),
|
||||
onConfirm = {
|
||||
viewModel.clearBookCache()
|
||||
showClearBookCacheDialog = false
|
||||
},
|
||||
onDismiss = { showClearBookCacheDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showClearReflowCacheDialog) {
|
||||
DangerousFolderActionDialog(
|
||||
title = context.getString(R.string.dialog_clear_reflow_cache),
|
||||
message = context.getString(R.string.dialog_clear_reflow_cache_desc),
|
||||
onConfirm = {
|
||||
viewModel.clearReflowCache()
|
||||
showClearReflowCacheDialog = false
|
||||
},
|
||||
onDismiss = { showClearReflowCacheDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showClearAllDataDialog) {
|
||||
ClearAllDataConfirmationDialog(
|
||||
onConfirm = {
|
||||
viewModel.deleteAllCloudAndLocalData()
|
||||
showClearAllDataDialog = false
|
||||
},
|
||||
onDismiss = { showClearAllDataDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showLanguageDialog) {
|
||||
LanguageSelectionDialog(onDismiss = { showLanguageDialog = false })
|
||||
}
|
||||
|
||||
if (showAppThemePanel) {
|
||||
AppThemeBottomSheet(
|
||||
uiState = uiState,
|
||||
onThemeModeChanged = viewModel::setAppThemeMode,
|
||||
onContrastOptionChanged = viewModel::setAppContrastOption,
|
||||
onTextDimFactorLightChanged = viewModel::setAppTextDimFactorLight,
|
||||
onTextDimFactorDarkChanged = viewModel::setAppTextDimFactorDark,
|
||||
onSeedColorChanged = viewModel::setAppSeedColor,
|
||||
onCustomThemeAdded = viewModel::addCustomAppTheme,
|
||||
onCustomThemeDeleted = viewModel::deleteCustomAppTheme,
|
||||
onDismiss = { showAppThemePanel = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showAboutDialog) {
|
||||
AboutDialog(onDismiss = { showAboutDialog = false })
|
||||
}
|
||||
|
||||
if (showSignOutConfirmDialog) {
|
||||
SignOutConfirmationDialog(
|
||||
onConfirm = {
|
||||
viewModel.signOut()
|
||||
showSignOutConfirmDialog = false
|
||||
},
|
||||
onDismiss = { showSignOutConfirmDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showUpgradeDialog) {
|
||||
UpgradeDialog(
|
||||
onConfirm = {
|
||||
showUpgradeDialog = false
|
||||
navController.navigate(AppDestinations.PRO_SCREEN_ROUTE)
|
||||
},
|
||||
onDismiss = { showUpgradeDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showTtsSettingsSheet) {
|
||||
TtsSettingsSheet(
|
||||
isVisible = true,
|
||||
onDismiss = { showTtsSettingsSheet = false },
|
||||
currentMode = ttsMode,
|
||||
onModeChange = { mode ->
|
||||
ttsMode = mode
|
||||
viewModel.ttsController.changeTtsMode(mode.name)
|
||||
},
|
||||
currentSpeakerId = ttsState.speakerId,
|
||||
onSpeakerChange = viewModel.ttsController::changeSpeaker,
|
||||
isTtsActive = ttsState.isPlaying,
|
||||
getAuthToken = { viewModel.getAuthToken() },
|
||||
bookTitle = "Reader defaults"
|
||||
)
|
||||
}
|
||||
|
||||
if (uiState.deviceLimitState.isLimitReached) {
|
||||
DeviceManagementScreen(
|
||||
devices = uiState.deviceLimitState.registeredDevices,
|
||||
onRemoveDevice = { deviceId -> viewModel.replaceDevice(deviceId) },
|
||||
isReplacing = uiState.isReplacingDevice
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RecentLimitDialog(
|
||||
currentLimit: Int,
|
||||
onSelect: (Int) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Recent files limit") },
|
||||
text = {
|
||||
androidx.compose.foundation.layout.Column {
|
||||
listOf(0, 10, 20, 50, 100).forEach { limit ->
|
||||
TextButton(onClick = { onSelect(limit) }) {
|
||||
val label = if (limit == 0) "No limit" else "$limit files"
|
||||
Text(if (currentLimit == limit) "$label selected" else label)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadAndroidEpubReaderDefaultSettings(
|
||||
context: Context,
|
||||
renderMode: RenderMode
|
||||
): ReaderSettings {
|
||||
val format = loadFormatSettings(context, ANDROID_SETTINGS_GLOBAL_BOOK_ID, isLocal = false)
|
||||
val horizontalMargin = (48f * format.horizontalMargin).roundToInt().coerceIn(0, 160)
|
||||
val verticalMargin = (48f * format.verticalMargin).roundToInt().coerceIn(0, 160)
|
||||
val base = ReaderSettings(
|
||||
fontSize = (18f * format.fontSize).roundToInt().coerceIn(12, 42),
|
||||
lineSpacing = (1.45f * format.lineHeight).coerceIn(1.0f, 2.8f),
|
||||
margin = max(horizontalMargin, verticalMargin),
|
||||
readingMode = renderMode.toSharedReaderReadingMode(),
|
||||
textAlign = format.textAlign.toSharedReaderTextAlign(),
|
||||
fontFamily = format.toSharedFontFamilyName(),
|
||||
paragraphSpacing = format.paragraphGap.coerceIn(0.5f, 2.5f),
|
||||
imageScale = format.imageSize.coerceIn(0.5f, 2.0f),
|
||||
horizontalMargin = horizontalMargin,
|
||||
verticalMargin = verticalMargin,
|
||||
themeId = loadReaderThemeId(context),
|
||||
textureAlpha = (1f - loadGlobalTextureTransparency(context)).coerceIn(0f, 1f),
|
||||
customFontPath = format.customPath?.takeIf { it.isNotBlank() },
|
||||
systemUiMode = loadSystemUiMode(context).toSharedSystemUiMode(),
|
||||
pageInfoMode = loadPageInfoMode(context).toSharedPageInfoMode(),
|
||||
pageInfoPosition = loadPageInfoPosition(context).toSharedPageInfoPosition(),
|
||||
seamlessChapterNavigation = loadPullToTurn(context),
|
||||
chapterTurnDragMultiplier = loadPullToTurnMultiplier(context)
|
||||
)
|
||||
return readerThemeById(base.themeId)?.toReaderSettings(base) ?: base
|
||||
}
|
||||
|
||||
private fun loadAndroidPdfReaderDefaultSettings(
|
||||
context: Context
|
||||
): ReaderSettings {
|
||||
val base = ReaderSettings(
|
||||
themeId = loadPdfThemeId(context),
|
||||
textureAlpha = (1f - loadGlobalTextureTransparency(context)).coerceIn(0f, 1f),
|
||||
systemUiMode = loadPdfSystemUiMode(context).toSharedSystemUiMode(),
|
||||
pdfVerticalPageGapVisible = loadPdfVerticalPageGapVisible(context),
|
||||
pdfPageNumberOverlayVisible = loadPdfPageNumberOverlayVisible(context)
|
||||
)
|
||||
return BuiltInPdfReaderThemes.firstOrNull { it.id == base.themeId }?.toReaderSettings(base) ?: base
|
||||
}
|
||||
|
||||
private fun saveAndroidEpubReaderDefaultSettings(
|
||||
context: Context,
|
||||
settings: ReaderSettings
|
||||
) {
|
||||
saveReaderSettings(
|
||||
context = context,
|
||||
fontSize = (settings.fontSize / 18f).coerceIn(0.65f, 2.4f),
|
||||
lineHeight = (settings.lineSpacing / 1.45f).coerceIn(0.7f, 2.0f),
|
||||
paragraphGap = settings.paragraphSpacing.coerceIn(0.5f, 2.5f),
|
||||
imageSize = settings.imageScale.coerceIn(0.5f, 2.0f),
|
||||
horizontalMargin = (settings.resolvedHorizontalMargin / 48f).coerceIn(0f, 3.4f),
|
||||
verticalMargin = (settings.resolvedVerticalMargin / 48f).coerceIn(0f, 3.4f),
|
||||
fontFamily = settings.toAndroidReaderFont(),
|
||||
customFontPath = settings.customFontPath,
|
||||
textAlign = settings.textAlign.toAndroidTextAlign()
|
||||
)
|
||||
saveSystemUiMode(context, settings.systemUiMode.toAndroidSystemUiMode())
|
||||
savePageInfoMode(context, settings.pageInfoMode.toAndroidPageInfoMode())
|
||||
savePageInfoPosition(context, settings.pageInfoPosition.toAndroidPageInfoPosition())
|
||||
savePullToTurn(context, settings.seamlessChapterNavigation)
|
||||
savePullToTurnMultiplier(context, settings.chapterTurnDragMultiplier)
|
||||
saveReaderThemeId(context, settings.themeId ?: "system")
|
||||
saveGlobalTextureTransparency(context, 1f - settings.textureAlpha.coerceIn(0f, 1f))
|
||||
}
|
||||
|
||||
private fun saveAndroidPdfReaderDefaultSettings(
|
||||
context: Context,
|
||||
settings: ReaderSettings
|
||||
) {
|
||||
savePdfSystemUiMode(context, settings.systemUiMode.toAndroidSystemUiMode())
|
||||
savePdfThemeId(context, settings.themeId ?: "no_theme")
|
||||
savePdfVerticalPageGapVisible(context, settings.pdfVerticalPageGapVisible)
|
||||
savePdfPageNumberOverlayVisible(context, settings.pdfPageNumberOverlayVisible)
|
||||
saveGlobalTextureTransparency(context, 1f - settings.textureAlpha.coerceIn(0f, 1f))
|
||||
}
|
||||
|
||||
private fun List<CustomFontEntity>.toSharedCustomFontItems(): List<CustomFontItem> {
|
||||
return filterNot { it.isDeleted }
|
||||
.sortedBy { it.displayName.lowercase() }
|
||||
.map { font ->
|
||||
CustomFontItem(
|
||||
id = font.id,
|
||||
displayName = font.displayName,
|
||||
fileName = font.fileName,
|
||||
fileExtension = font.fileExtension,
|
||||
path = font.path,
|
||||
timestamp = font.timestamp,
|
||||
isDeleted = font.isDeleted
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun AndroidFormatSettings.toSharedFontFamilyName(): String {
|
||||
return customPath?.substringAfterLast('/')?.substringAfterLast('\\')?.takeIf { it.isNotBlank() }
|
||||
?: when (font) {
|
||||
AndroidReaderFont.ORIGINAL -> "Default"
|
||||
AndroidReaderFont.MERRIWEATHER,
|
||||
AndroidReaderFont.LORA -> "Serif"
|
||||
AndroidReaderFont.LATO,
|
||||
AndroidReaderFont.LEXEND -> "Sans"
|
||||
AndroidReaderFont.ROBOTO_MONO -> "Mono"
|
||||
}
|
||||
}
|
||||
|
||||
private fun AndroidReaderTextAlign.toSharedReaderTextAlign(): SharedReaderTextAlign {
|
||||
return when (this) {
|
||||
AndroidReaderTextAlign.JUSTIFY -> SharedReaderTextAlign.JUSTIFY
|
||||
AndroidReaderTextAlign.DEFAULT,
|
||||
AndroidReaderTextAlign.LEFT -> SharedReaderTextAlign.START
|
||||
}
|
||||
}
|
||||
|
||||
private fun ReaderSettings.toAndroidReaderFont(): AndroidReaderFont {
|
||||
return when (fontFamily) {
|
||||
"Serif" -> AndroidReaderFont.LORA
|
||||
"Sans" -> AndroidReaderFont.LATO
|
||||
"Mono" -> AndroidReaderFont.ROBOTO_MONO
|
||||
else -> AndroidReaderFont.ORIGINAL
|
||||
}
|
||||
}
|
||||
|
||||
private fun SharedReaderTextAlign.toAndroidTextAlign(): AndroidReaderTextAlign {
|
||||
return when (this) {
|
||||
SharedReaderTextAlign.JUSTIFY -> AndroidReaderTextAlign.JUSTIFY
|
||||
SharedReaderTextAlign.CENTER,
|
||||
SharedReaderTextAlign.START -> AndroidReaderTextAlign.LEFT
|
||||
}
|
||||
}
|
||||
|
||||
private fun RenderMode.toSharedReaderReadingMode(): ReaderReadingMode {
|
||||
return when (this) {
|
||||
RenderMode.PAGINATED -> ReaderReadingMode.PAGINATED
|
||||
RenderMode.VERTICAL_SCROLL -> ReaderReadingMode.VERTICAL
|
||||
}
|
||||
}
|
||||
|
||||
private fun ReaderSettings.toAndroidRenderMode(): RenderMode {
|
||||
return when (readingMode) {
|
||||
ReaderReadingMode.PAGINATED -> RenderMode.PAGINATED
|
||||
ReaderReadingMode.VERTICAL -> RenderMode.VERTICAL_SCROLL
|
||||
}
|
||||
}
|
||||
|
||||
private fun AndroidSystemUiMode.toSharedSystemUiMode(): SharedSystemUiMode {
|
||||
return SharedSystemUiMode.valueOf(name)
|
||||
}
|
||||
|
||||
private fun SharedSystemUiMode.toAndroidSystemUiMode(): AndroidSystemUiMode {
|
||||
return AndroidSystemUiMode.valueOf(name)
|
||||
}
|
||||
|
||||
private fun AndroidPageInfoMode.toSharedPageInfoMode(): SharedPageInfoMode {
|
||||
return SharedPageInfoMode.valueOf(name)
|
||||
}
|
||||
|
||||
private fun SharedPageInfoMode.toAndroidPageInfoMode(): AndroidPageInfoMode {
|
||||
return AndroidPageInfoMode.valueOf(name)
|
||||
}
|
||||
|
||||
private fun AndroidPageInfoPosition.toSharedPageInfoPosition(): SharedPageInfoPosition {
|
||||
return SharedPageInfoPosition.valueOf(name)
|
||||
}
|
||||
|
||||
private fun SharedPageInfoPosition.toAndroidPageInfoPosition(): AndroidPageInfoPosition {
|
||||
return AndroidPageInfoPosition.valueOf(name)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue