v1.0.45-oss (#221)
* Implemented ML-based comic panel detection and a panel popup viewer. * Optimized `ComicPanelDetector` for performance and memory efficiency. * Optimized comic panel detection performance by introducing a dedicated single-thread dispatcher for ML tasks, replacing mutex-based synchronization. * Refactored system UI handling and layout padding in `PdfViewerScreen`. * Refactor `PdfViewerScreen.kt` by extracting components and logic into specialized files. * replace hardcoded padding with dynamic header height and adjust IME layout logic * Improve `PdfTextBox` interaction and visual consistency during zoom and pan. * improve PDF text box dragging and scaling behavior across zoom levels * optimize color scheme calculation using remember and expand text dimming coverage * Implement in-app language selection and per-app language preferences. * Improve PDF lock stability in `PdfVerticalReader` * Implement "Preserve Image Colors" option for PDF themes * Optimize TOC locate in ReaderDrawer * Update library and home screen UI components * smoother page turn animation in epub pagination mode * Implement automatic page skipping for TTS when no text is found in PDF viewer * Refine status bar handling and window insets across main screens * Optimize ViewModel initialization and integrate AndroidX SplashScreen * Move ComicPanelDetector to debug source set and introduce IPanelDetector interface * Optimize TOC scrolling in PdfNavigationDrawerContent * Bump version to 1.0.45(45)
This commit is contained in:
parent
71e3614ad6
commit
cb7de86224
33 changed files with 4795 additions and 3447 deletions
|
|
@ -28,10 +28,12 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -55,8 +57,10 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Cloud
|
||||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.PushPin
|
||||
import androidx.compose.material.icons.filled.SelectAll
|
||||
|
|
@ -69,6 +73,7 @@ import androidx.compose.material3.FilledTonalButton
|
|||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
|
|
@ -85,6 +90,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
|
|
@ -100,6 +106,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
|
@ -111,6 +118,7 @@ import java.util.Date
|
|||
import java.util.Locale
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
internal const val PRIVACY_POLICY_URL = "https://aryan-raj3112.github.io/reader-policy/privacy-policy.html"
|
||||
internal const val TERMS_URL = "https://aryan-raj3112.github.io/reader-policy/terms-and-conditions.html"
|
||||
|
|
@ -254,15 +262,15 @@ fun CustomTopAppBar(
|
|||
actions: @Composable RowScope.() -> Unit = {}
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shadowElevation = 2.dp
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.fillMaxWidth()
|
||||
.statusBarsPadding()
|
||||
.height(56.dp)
|
||||
.padding(horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
@ -874,20 +882,168 @@ fun AutoSizeText(
|
|||
|
||||
@Composable
|
||||
fun FileTypeBadge(type: FileType, modifier: Modifier = Modifier, overlay: Boolean = false) {
|
||||
val containerColor = if (overlay) androidx.compose.ui.graphics.Color.Black.copy(alpha = 0.6f) else MaterialTheme.colorScheme.secondaryContainer
|
||||
val contentColor = if (overlay) androidx.compose.ui.graphics.Color.White else MaterialTheme.colorScheme.onSecondaryContainer
|
||||
val containerColor = if (overlay) Color.Black.copy(alpha = 0.6f) else MaterialTheme.colorScheme.secondaryContainer
|
||||
val contentColor = if (overlay) Color.White else MaterialTheme.colorScheme.onSecondaryContainer
|
||||
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
shape = RoundedCornerShape(50),
|
||||
color = containerColor,
|
||||
contentColor = contentColor
|
||||
contentColor = contentColor,
|
||||
border = if (overlay) BorderStroke(1.dp, Color.White.copy(alpha = 0.3f)) else null
|
||||
) {
|
||||
Text(
|
||||
text = type.name.uppercase(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
style = MaterialTheme.typography.labelSmall.copy(letterSpacing = 1.sp),
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val UNKNOWN_AUTHOR_LABEL = "No author listed"
|
||||
|
||||
fun RecentFileItem.cardTitle(): String {
|
||||
return customName ?: title?.takeIf { it.isNotBlank() } ?: displayName
|
||||
}
|
||||
|
||||
fun RecentFileItem.cardAuthor(): String {
|
||||
return author
|
||||
?.takeIf { it.isNotBlank() && !it.equals("Unknown", ignoreCase = true) }
|
||||
?: UNKNOWN_AUTHOR_LABEL
|
||||
}
|
||||
|
||||
fun RecentFileItem.progressPercentValue(): Int {
|
||||
return (progressPercentage ?: 0f).coerceIn(0f, 100f).roundToInt()
|
||||
}
|
||||
|
||||
fun RecentFileItem.progressFraction(): Float {
|
||||
return progressPercentValue() / 100f
|
||||
}
|
||||
|
||||
fun RecentFileItem.isOpdsStream(): Boolean {
|
||||
return uriString?.startsWith("opds-pse://") == true
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun statusBadgeColors(overlay: Boolean): Pair<Color, Color> {
|
||||
val container = if (overlay) {
|
||||
MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.92f)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceContainerHighest
|
||||
}
|
||||
val content = if (overlay) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
return container to content
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StatusIconBadge(
|
||||
icon: ImageVector,
|
||||
contentDescription: String,
|
||||
modifier: Modifier = Modifier,
|
||||
overlay: Boolean = false,
|
||||
) {
|
||||
val (containerColor, contentColor) = statusBadgeColors(overlay)
|
||||
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = containerColor,
|
||||
contentColor = contentColor,
|
||||
tonalElevation = if (overlay) 0.dp else 2.dp,
|
||||
shadowElevation = if (overlay) 0.dp else 1.dp,
|
||||
border = if (overlay) BorderStroke(1.dp, Color.White.copy(alpha = 0.12f)) else null
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = contentDescription,
|
||||
modifier = Modifier.padding(6.dp).size(14.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FileStatusBadges(
|
||||
item: RecentFileItem,
|
||||
isPinned: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
overlay: Boolean = false,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (item.sourceFolderUri != null) {
|
||||
StatusIconBadge(
|
||||
icon = Icons.Default.Folder,
|
||||
contentDescription = stringResource(R.string.local_folder),
|
||||
overlay = overlay
|
||||
)
|
||||
}
|
||||
if (item.isOpdsStream()) {
|
||||
StatusIconBadge(
|
||||
icon = Icons.Default.Cloud,
|
||||
contentDescription = stringResource(R.string.opds_stream),
|
||||
overlay = overlay
|
||||
)
|
||||
}
|
||||
if (isPinned) {
|
||||
StatusIconBadge(
|
||||
icon = Icons.Default.PushPin,
|
||||
contentDescription = stringResource(R.string.pinned),
|
||||
overlay = overlay
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReadingProgressSection(
|
||||
progressPercentage: Float?,
|
||||
modifier: Modifier = Modifier,
|
||||
label: String? = null,
|
||||
compact: Boolean = false,
|
||||
) {
|
||||
val percent = (progressPercentage ?: 0f).coerceIn(0f, 100f).roundToInt()
|
||||
val progress = percent / 100f
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (label != null) {
|
||||
Text(
|
||||
text = label,
|
||||
style = if (compact) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Surface(
|
||||
shape = RoundedCornerShape(50),
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
) {
|
||||
Text(
|
||||
text = "$percent%",
|
||||
style = if (compact) MaterialTheme.typography.labelSmall else MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(if (compact) 6.dp else 8.dp))
|
||||
LinearProgressIndicator(
|
||||
progress = { progress },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(if (compact) 5.dp else 6.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
trackColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.4f)
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue