V1.0.46 oss (#253)
* add a UI trigger for demo annotations * Implement internal link navigation * Simplify bottom padding logic in `EpubReaderScreen` for vertical scroll mode * Add support for Calibre metadata * Implement advanced library management with Room-backed collections, tags, and smart rules. * Display book series information in book details and remove tags from home screen * Implement zoom reset functionality in PDF viewer * Add a "Pages" thumbnail view to the PDF navigation drawer * Implement jump-back navigation in PDF viewer * MainViewModel: disable FolderSyncWorker, add log export, and enhance PDF position logging * Replace Gson with Kotlin Serialization in SmartCollectionEngine * Implement ONNX-based speech bubble detection * Implement speech bubble detection in PDF viewer * Implement "Smart Comic Zoom" feature for PDF manga/comic reading * Implement reader session persistence and restoration in `MainViewModel` * Add tap-to-turn page feature to PDF viewer * Improve book cache management and recovery * Refactor top overlay padding logic in `PdfViewerScreen` * Implement stylus eraser support in PDF reader * Refactor ReaderTextFormatPanel to use ModalBottomSheet and add new formatting controls * Introduce a customizable horizontal margin setting for the EPUB reader * Refine folder sync and metadata handling for better conflict resolution and stability. * Introduce user-adjustable image scaling for the EPUB reader * Use maxWidthPx as default width fallback for blocks * Improve cross-page text selection and header styling in the paginated reader * Refactor speech bubble detection and UI to support segmentation masks and interactive scaling * Improve speech bubble detection masking and rendering quality * Update ONNX speech bubble detector to use `.ort` model and optimize inference * Improve pagination accuracy * Implement hierarchical folder navigation for the library. * Refactor library item layout for improved space efficiency * Add support for browsing and downloading Google Fonts * Persist library landing state and add shelf search functionality * Fix base tts sample. * Move SpeechBubbleDetector to main source set and update ONNX dependency * Implement a "locate" feature and improve synchronization for TTS (Text-to-Speech) playback across EPUB and PDF readers. * Refine TTS synchronization and voice management in the EPUB reader * Adjust OCR checks for OSS flavor and optimize external dictionary intent flags * Add "Locate" button to PDF drawer's pages tab and move the page number to bottom right of thumbnail * fix various crashes * Refactor SpeechBubbleDetector into product flavors * Implement speech bubble detection caching and background prefetching * Implement on-demand download for Bubble Zoom ML model * Add smooth animation for PDF speech bubble expansion * fixes #252 * hide Google Fonts option, in FontsScreen, for offline variant * Bump version to 1.0.46(46)
This commit is contained in:
parent
579b6f25bf
commit
d16bdd70d4
67 changed files with 9724 additions and 1890 deletions
|
|
@ -17,9 +17,13 @@
|
|||
*
|
||||
* mail: epistemereader@gmail.com
|
||||
*/
|
||||
// FontsScreen.kt
|
||||
@file:Suppress("KotlinConstantConditions")
|
||||
|
||||
package com.aryan.reader
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -34,24 +38,32 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.Check
|
||||
import androidx.compose.material.icons.filled.CloudDownload
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -59,6 +71,8 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
|
|
@ -69,6 +83,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.aryan.reader.data.CustomFontEntity
|
||||
import java.io.File
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FontsScreen(
|
||||
viewModel: MainViewModel,
|
||||
|
|
@ -76,24 +91,21 @@ fun FontsScreen(
|
|||
) {
|
||||
val fonts: List<CustomFontEntity> by viewModel.customFonts.collectAsStateWithLifecycle()
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
|
||||
// Dialog state
|
||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||
var fontToDelete by remember { mutableStateOf<CustomFontEntity?>(null) }
|
||||
var showGoogleFontsSheet by remember { mutableStateOf(false) }
|
||||
|
||||
val pickFontLauncher = rememberFilePickerLauncher { uris ->
|
||||
uris.firstOrNull()?.let { viewModel.importFont(it) }
|
||||
}
|
||||
|
||||
// Font mime types filter
|
||||
val fontMimeTypes = arrayOf(
|
||||
"font/ttf",
|
||||
"font/otf",
|
||||
"font/woff2",
|
||||
"application/x-font-ttf",
|
||||
"application/x-font-otf",
|
||||
"application/font-woff2",
|
||||
"application/vnd.ms-opentype",
|
||||
"font/ttf", "font/otf", "font/woff2",
|
||||
"application/x-font-ttf", "application/x-font-otf",
|
||||
"application/font-woff2", "application/vnd.ms-opentype",
|
||||
"application/x-font-opentype"
|
||||
)
|
||||
|
||||
|
|
@ -111,11 +123,24 @@ fun FontsScreen(
|
|||
},
|
||||
floatingActionButton = {
|
||||
if (fonts.isNotEmpty()) {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = { pickFontLauncher.launch(fontMimeTypes) },
|
||||
icon = { Icon(Icons.Default.Add, contentDescription = null) },
|
||||
text = { Text(stringResource(R.string.import_font)) }
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = { showGoogleFontsSheet = true },
|
||||
icon = { Icon(Icons.Default.CloudDownload, contentDescription = null) },
|
||||
text = { Text("Google Fonts") },
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = { pickFontLauncher.launch(fontMimeTypes) },
|
||||
icon = { Icon(Icons.Default.Add, contentDescription = null) },
|
||||
text = { Text(stringResource(R.string.import_font)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
|
|
@ -125,7 +150,9 @@ fun FontsScreen(
|
|||
title = stringResource(R.string.no_custom_fonts),
|
||||
message = stringResource(R.string.import_fonts_desc),
|
||||
onSelectFileClick = { pickFontLauncher.launch(fontMimeTypes) },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
secondaryButtonText = "Browse Google Fonts",
|
||||
onSecondaryClick = { showGoogleFontsSheet = true }
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
|
|
@ -155,7 +182,6 @@ fun FontsScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
// Banner messages removed as requested
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -173,8 +199,172 @@ fun FontsScreen(
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showGoogleFontsSheet) {
|
||||
GoogleFontsBottomSheet(
|
||||
onDismiss = { showGoogleFontsSheet = false },
|
||||
existingFonts = fonts,
|
||||
getFullFontList = { viewModel.loadGoogleFontsList(context) },
|
||||
onDownloadFont = { fontName, onComplete ->
|
||||
viewModel.downloadGoogleFont(fontName, onComplete)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GoogleFontsBottomSheet(
|
||||
onDismiss: () -> Unit,
|
||||
existingFonts: List<CustomFontEntity>,
|
||||
getFullFontList: () -> List<String>,
|
||||
onDownloadFont: (String, () -> Unit) -> Unit
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
var downloadingFontName by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
// Curated presets to show when search is empty
|
||||
val popularPresets = remember {
|
||||
listOf(
|
||||
"Merriweather", "Open Sans", "Playfair Display", "Montserrat", "Oswald", "Raleway", "Nunito",
|
||||
"Poppins", "Ubuntu", "Fira Sans", "Quicksand", "Crimson Text",
|
||||
"Literata", "EB Garamond", "Libre Baskerville", "Inter", "Work Sans"
|
||||
)
|
||||
}
|
||||
|
||||
// Lazy evaluation of the full list only when typing
|
||||
val displayList = remember(searchQuery) {
|
||||
if (searchQuery.isBlank()) {
|
||||
popularPresets
|
||||
} else {
|
||||
val allFonts = getFullFontList()
|
||||
allFonts.filter { it.contains(searchQuery, ignoreCase = true) }.take(50) // Limit to 50 for performance
|
||||
}
|
||||
}
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState,
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Browse Google Fonts",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = searchQuery,
|
||||
onValueChange = { searchQuery = it },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text("Search 1900+ fonts...") },
|
||||
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentPadding = PaddingValues(bottom = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
if (searchQuery.isBlank()) {
|
||||
item {
|
||||
Text(
|
||||
text = "Popular Choices",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
} else if (displayList.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "No fonts found matching '$searchQuery'",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
items(displayList) { fontName ->
|
||||
val isDownloaded = remember(existingFonts, fontName) {
|
||||
existingFonts.any { it.displayName.equals(fontName, ignoreCase = true) }
|
||||
}
|
||||
val isDownloading = downloadingFontName == fontName
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(enabled = !isDownloaded && !isDownloading) {
|
||||
downloadingFontName = fontName
|
||||
onDownloadFont(fontName) {
|
||||
if (downloadingFontName == fontName) {
|
||||
downloadingFontName = null
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(
|
||||
if (isDownloaded) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.2f)
|
||||
else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = fontName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = if (isDownloaded) FontWeight.Bold else FontWeight.Medium,
|
||||
color = if (isDownloaded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
Box(modifier = Modifier.padding(start = 12.dp)) {
|
||||
when {
|
||||
isDownloaded -> {
|
||||
Icon(
|
||||
Icons.Default.Check,
|
||||
contentDescription = "Already Downloaded",
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
isDownloading -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(20.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Icon(
|
||||
Icons.Default.CloudDownload,
|
||||
contentDescription = "Download",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Existing unchanged components
|
||||
@Composable
|
||||
fun FontListItem(
|
||||
font: CustomFontEntity,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue