General improvements (#316)

* Added support for right text alignment in epub reader

* Added tabs management to PDF navigation drawer

* Implemented unified selection menu placement logic

* Added reset functionality for toolbar customization

* Improved highlight filtering in epub pagination

* Migrated hardcoded UI strings to string resources

* Extracted desktop application logic from Main.kt into modular files

* Refactored Main.kt by extracting PDF and EPUB logic into specialized files

* Added Spanish language support

* Added system default option to app language selection
This commit is contained in:
Aryan 2026-05-16 16:19:31 +05:30 committed by GitHub
parent 759d4b73a0
commit 056485a140
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 9184 additions and 5947 deletions

View file

@ -0,0 +1,202 @@
package com.aryan.reader.desktop
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.ImageShader
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.aryan.reader.shared.BuiltInPdfReaderThemes
import com.aryan.reader.shared.PdfDisplayMode
import com.aryan.reader.shared.ReaderTheme
import com.aryan.reader.shared.reader.ReaderSettings
internal enum class DesktopPdfInspectorTab(val title: String) {
VIEW("View"),
MARKUP("Markup"),
ASSIST("Assist")
}
internal data class DesktopPdfThemeStyle(
val theme: ReaderTheme,
val viewerBackgroundColor: Color,
val pageBackgroundColor: Color,
val colorFilter: ColorFilter?,
val textureBitmap: ImageBitmap?,
val textureAlpha: Float,
val textureBlendMode: BlendMode
)
@Composable
internal fun DesktopPdfThemedPageImage(
bitmap: ImageBitmap,
contentDescription: String,
themeStyle: DesktopPdfThemeStyle,
modifier: Modifier = Modifier
) {
Box(modifier = modifier.background(themeStyle.pageBackgroundColor)) {
Image(
bitmap = bitmap,
contentDescription = contentDescription,
colorFilter = themeStyle.colorFilter,
modifier = Modifier.fillMaxSize()
)
val textureBitmap = themeStyle.textureBitmap
if (textureBitmap != null && themeStyle.textureAlpha > 0f) {
Canvas(modifier = Modifier.fillMaxSize()) {
drawRect(
brush = ShaderBrush(ImageShader(textureBitmap, TileMode.Repeated, TileMode.Repeated)),
size = size,
blendMode = themeStyle.textureBlendMode,
alpha = themeStyle.textureAlpha
)
}
}
}
}
internal fun ReaderSettings?.toDesktopPdfReaderSettings(): ReaderSettings {
val defaults = ReaderSettings(themeId = "no_theme")
val settings = this ?: defaults
val themeId = settings.themeId
val hasPdfTheme = BuiltInPdfReaderThemes.any { it.id == themeId }
val hasCustomColors = settings.backgroundColorArgb != null && settings.textColorArgb != null
return settings.copy(
themeId = when {
themeId == null -> "no_theme"
hasPdfTheme || hasCustomColors -> themeId
else -> "no_theme"
}
)
}
internal fun ReaderSettings.toDesktopPdfThemeStyle(displayMode: PdfDisplayMode): DesktopPdfThemeStyle {
val theme = toDesktopPdfTheme()
val pageBackground = desktopPdfPageBackgroundColor(theme, displayMode)
val isDarkTexture = theme.isDark || theme.id == "reverse"
return DesktopPdfThemeStyle(
theme = theme,
viewerBackgroundColor = pageBackground,
pageBackgroundColor = pageBackground,
colorFilter = theme.toDesktopPdfColorFilter(),
textureBitmap = DesktopReaderTextures.imageBitmapFor(textureId),
textureAlpha = if (textureId == null) 0f else textureAlpha.coerceIn(0f, 1f),
textureBlendMode = if (isDarkTexture) BlendMode.Screen else BlendMode.Multiply
)
}
private fun ReaderSettings.toDesktopPdfTheme(): ReaderTheme {
BuiltInPdfReaderThemes.firstOrNull { it.id == themeId }?.let { return it }
val background = backgroundColorArgb?.toComposeColor()
val text = textColorArgb?.toComposeColor()
return if (background != null && text != null) {
ReaderTheme(
id = themeId ?: "desktop_pdf_custom",
name = "Custom",
backgroundColor = background,
textColor = text,
isDark = darkMode,
textureId = textureId,
isCustom = true
)
} else {
BuiltInPdfReaderThemes.first()
}
}
private fun ReaderTheme.toDesktopPdfColorFilter(): ColorFilter? {
return when (id) {
"no_theme", "system" -> null
"reverse" -> {
val colorMatrix = floatArrayOf(
-1f, 0f, 0f, 0f, 255f,
0f, -1f, 0f, 0f, 255f,
0f, 0f, -1f, 0f, 255f,
0f, 0f, 0f, 1f, 0f
)
ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
}
else -> {
if (!backgroundColor.isSpecified || !textColor.isSpecified) return null
val bgR = backgroundColor.red * 255f
val bgG = backgroundColor.green * 255f
val bgB = backgroundColor.blue * 255f
val fgR = textColor.red * 255f
val fgG = textColor.green * 255f
val fgB = textColor.blue * 255f
val dr = (bgR - fgR) / 255f
val dg = (bgG - fgG) / 255f
val db = (bgB - fgB) / 255f
val lumR = 0.2126f
val lumG = 0.7152f
val lumB = 0.0722f
val colorMatrix = floatArrayOf(
dr * lumR, dr * lumG, dr * lumB, 0f, fgR,
dg * lumR, dg * lumG, dg * lumB, 0f, fgG,
db * lumR, db * lumG, db * lumB, 0f, fgB,
0f, 0f, 0f, 1f, 0f
)
ColorFilter.colorMatrix(ColorMatrix(colorMatrix))
}
}
}
@Composable
internal fun DesktopPdfInspectorSection(
title: String,
content: @Composable ColumnScope.() -> Unit
) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold)
content()
}
}
@Composable
internal fun DesktopPdfVisualOptionSwitch(
title: String,
description: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(title, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
Text(
description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Switch(checked = checked, onCheckedChange = onCheckedChange)
}
}
private fun Long.toComposeColor(): Color {
return Color(this and 0xFFFFFFFFL)
}