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:
parent
759d4b73a0
commit
056485a140
77 changed files with 9184 additions and 5947 deletions
|
|
@ -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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue