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:
Aryan 2026-04-22 14:11:52 +05:30 committed by GitHub
parent 71e3614ad6
commit cb7de86224
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 4795 additions and 3447 deletions

View file

@ -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)
)
}
}