* 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
202 lines
7.2 KiB
Kotlin
202 lines
7.2 KiB
Kotlin
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)
|
|
}
|