Linux support (#381)
* Add desktop release CI and support for Arch Linux packaging * Make Gradle wrapper executable in desktop-release workflow * Make Gradle wrapper executable in desktop-release workflow * Make Gradle wrapper executable in desktop-release workflow * Configure Gradle and update Java environment in desktop-release workflow * Update Java setup and AUR packaging in desktop release workflow * Update Java setup and AUR packaging in desktop release workflow * Add MSIX packaging support for Windows desktop distribution * Update AUR packaging metadata and validation * Use spine toc attribute for NCX resolution * crash fixes * Implement automatic discovery and injection of EPUB font face siblings * Enhance custom font support with family grouping and variable font handling * Optimize metadata loading and improve TTS highlighting * Add keyboard navigation support for EPUB reader * Refine PDF spread page sizing to respect aspect ratios * Implement responsive maximum height for reader popups and sheets * Handle TTS generation failures by skipping problematic chunks * Refactor PDF tile rendering logic and zoom indicator behavior * Prefer block and offset locators over page index in native vertical flow * Implement save and share actions for original book files * Add Estonian language support * Implement temporary viewing mode for external files * Implement direct opening for temporary external files without library persistence * fix failing tests * Import SharedFileCapabilities in DesktopLibraryUi * Improve native vertical reader progress, persistence, and image support * Center target in viewport for native vertical reader and support animated scrolling
This commit is contained in:
parent
a13d6599d1
commit
625a4d5d2e
102 changed files with 6012 additions and 687 deletions
|
|
@ -1,5 +1,8 @@
|
|||
package com.aryan.reader.shared
|
||||
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
||||
data class CustomFontItem(
|
||||
val id: String,
|
||||
val displayName: String,
|
||||
|
|
@ -10,3 +13,76 @@ data class CustomFontItem(
|
|||
val isDeleted: Boolean = false
|
||||
)
|
||||
|
||||
data class CustomFontVariantItem(
|
||||
val font: CustomFontItem,
|
||||
val variant: FontVariant?
|
||||
)
|
||||
|
||||
data class CustomFontFamilyItem(
|
||||
val familyName: String,
|
||||
val variants: List<CustomFontVariantItem>
|
||||
)
|
||||
|
||||
fun List<CustomFontItem>.groupByFamily(): List<CustomFontFamilyItem> {
|
||||
val families = this.groupBy {
|
||||
it.displayName.familyFilenameSignature().takeIf { s -> s.isNotBlank() } ?: it.displayName
|
||||
}
|
||||
|
||||
return families.map { (familyName, fonts) ->
|
||||
val variants = fonts.map { font ->
|
||||
CustomFontVariantItem(
|
||||
font = font,
|
||||
variant = font.displayName.detectFontVariant()
|
||||
)
|
||||
}
|
||||
CustomFontFamilyItem(
|
||||
familyName = familyName.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() },
|
||||
variants = variants
|
||||
)
|
||||
}.sortedBy { it.familyName }
|
||||
}
|
||||
|
||||
fun CustomFontVariantItem.fontFaceLabel(): String {
|
||||
val variant = variant ?: return "Regular"
|
||||
return when {
|
||||
variant.weight.weight >= FontWeight.Bold.weight && variant.style == FontStyle.Italic -> "Bold Italic"
|
||||
variant.weight.weight >= FontWeight.Bold.weight -> "Bold"
|
||||
variant.style == FontStyle.Italic -> "Italic"
|
||||
variant.weight == FontWeight.Normal -> "Regular"
|
||||
variant.weight.weight < FontWeight.Normal.weight -> variant.weight.fontWeightLabel()
|
||||
else -> variant.weight.fontWeightLabel()
|
||||
}
|
||||
}
|
||||
|
||||
fun CustomFontFamilyItem.fontFaceSummary(): String {
|
||||
return variants
|
||||
.sortedWith(compareBy<CustomFontVariantItem> {
|
||||
it.variant?.style == FontStyle.Italic
|
||||
}.thenBy {
|
||||
it.variant?.weight?.weight ?: FontWeight.Normal.weight
|
||||
})
|
||||
.map { it.fontFaceLabel() }
|
||||
.distinct()
|
||||
.joinToString()
|
||||
}
|
||||
|
||||
fun CustomFontFamilyItem.hasVariableWeightFace(): Boolean {
|
||||
return variants.any { variant ->
|
||||
variant.font.displayName.supportsVariableWeightAxis() || variant.font.fileName.supportsVariableWeightAxis()
|
||||
}
|
||||
}
|
||||
|
||||
private fun FontWeight.fontWeightLabel(): String {
|
||||
return when (this) {
|
||||
FontWeight.Thin -> "Thin"
|
||||
FontWeight.ExtraLight -> "Extra Light"
|
||||
FontWeight.Light -> "Light"
|
||||
FontWeight.Normal -> "Regular"
|
||||
FontWeight.Medium -> "Medium"
|
||||
FontWeight.SemiBold -> "Semi Bold"
|
||||
FontWeight.Bold -> "Bold"
|
||||
FontWeight.ExtraBold -> "Extra Bold"
|
||||
FontWeight.Black -> "Black"
|
||||
else -> weight.toString()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,149 @@
|
|||
package com.aryan.reader.shared
|
||||
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
||||
data class FontVariant(
|
||||
val weight: FontWeight,
|
||||
val style: FontStyle
|
||||
)
|
||||
|
||||
private val filenameSeparatorsRegex = Regex("""[\s._,-]+""")
|
||||
|
||||
fun String.familyFilenameSignature(): String {
|
||||
val tokens = filenameTokens()
|
||||
val variantIndexes = tokens.variantTokenIndexes()
|
||||
val signatureTokens = tokens.filterIndexed { index, token ->
|
||||
token.isNotBlank() && index !in variantIndexes
|
||||
}
|
||||
return signatureTokens.joinToString(separator = " ")
|
||||
}
|
||||
|
||||
fun String.supportsVariableWeightAxis(): Boolean {
|
||||
return filenameTokens().any { it == "wght" }
|
||||
}
|
||||
|
||||
fun String.fontWeightCssDescriptor(fallbackWeight: FontWeight = FontWeight.Normal): String {
|
||||
return if (supportsVariableWeightAxis()) {
|
||||
"100 900"
|
||||
} else {
|
||||
fallbackWeight.weight.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<String>.variantTokenIndexes(): Set<Int> {
|
||||
val indexes = mutableSetOf<Int>()
|
||||
forEachIndexed { index, token ->
|
||||
if (token in singleVariantTokens || token.toIntOrNull()?.isCssFontWeight() == true) {
|
||||
indexes += index
|
||||
}
|
||||
val next = getOrNull(index + 1) ?: return@forEachIndexed
|
||||
if ("$token$next" in compoundVariantTokens) {
|
||||
indexes += index
|
||||
indexes += index + 1
|
||||
}
|
||||
}
|
||||
return indexes
|
||||
}
|
||||
|
||||
private fun Int.isCssFontWeight(): Boolean = this in 100..900 && this % 100 == 0
|
||||
|
||||
private fun List<String>.containsCompoundToken(compoundTokens: Set<String>): Boolean {
|
||||
return windowed(size = 2, step = 1, partialWindows = false)
|
||||
.any { (first, second) -> "$first$second" in compoundTokens }
|
||||
}
|
||||
|
||||
private fun List<String>.detectedWeight(): FontWeight {
|
||||
val compactTokens = buildList {
|
||||
addAll(this@detectedWeight)
|
||||
this@detectedWeight.windowed(size = 2, step = 1, partialWindows = false)
|
||||
.forEach { (first, second) -> add("$first$second") }
|
||||
}
|
||||
return compactTokens.asSequence()
|
||||
.mapNotNull { token ->
|
||||
tokenWeightMap[token]
|
||||
?: compoundTokenWeightMap[token]
|
||||
?: token.toIntOrNull()?.takeIf { it.isCssFontWeight() }?.let(::FontWeight)
|
||||
}
|
||||
.maxByOrNull { it.weight }
|
||||
?: FontWeight.Normal
|
||||
}
|
||||
|
||||
private fun List<String>.detectedStyle(): FontStyle {
|
||||
return if (any { it in italicTokens } || containsCompoundToken(compoundItalicTokens)) {
|
||||
FontStyle.Italic
|
||||
} else {
|
||||
FontStyle.Normal
|
||||
}
|
||||
}
|
||||
|
||||
fun String.detectFontVariant(): FontVariant? {
|
||||
val tokens = filenameTokens()
|
||||
.filter { it.isNotBlank() }
|
||||
if (tokens.isEmpty()) return null
|
||||
|
||||
return FontVariant(weight = tokens.detectedWeight(), style = tokens.detectedStyle())
|
||||
}
|
||||
|
||||
private fun String.filenameTokens(): List<String> {
|
||||
return this
|
||||
.replace(Regex("""(?i)variablefont"""), " variablefont ")
|
||||
.replace(Regex("""(?<=[a-z])(?=[A-Z])"""), "-")
|
||||
.lowercase()
|
||||
.split(filenameSeparatorsRegex)
|
||||
}
|
||||
|
||||
private val italicTokens = setOf("italic", "ital", "oblique", "obliq", "it", "itallic", "italics", "slanted", "slant")
|
||||
|
||||
private val tokenWeightMap = mapOf(
|
||||
"thin" to FontWeight.Thin,
|
||||
"hairline" to FontWeight.Thin,
|
||||
"extralight" to FontWeight.ExtraLight,
|
||||
"ultralight" to FontWeight.ExtraLight,
|
||||
"light" to FontWeight.Light,
|
||||
"regular" to FontWeight.Normal,
|
||||
"normal" to FontWeight.Normal,
|
||||
"roman" to FontWeight.Normal,
|
||||
"book" to FontWeight.Normal,
|
||||
"medium" to FontWeight.Medium,
|
||||
"semibold" to FontWeight.SemiBold,
|
||||
"demibold" to FontWeight.SemiBold,
|
||||
"bold" to FontWeight.Bold,
|
||||
"extrabold" to FontWeight.ExtraBold,
|
||||
"ultrabold" to FontWeight.ExtraBold,
|
||||
"black" to FontWeight.Black,
|
||||
"heavy" to FontWeight.Black
|
||||
)
|
||||
|
||||
private val variableFontTokens = setOf(
|
||||
"variablefont",
|
||||
"vf",
|
||||
"variable",
|
||||
"wght",
|
||||
"wdth",
|
||||
"opsz",
|
||||
"slnt",
|
||||
"grad",
|
||||
"xtra",
|
||||
"xopq",
|
||||
"yopq",
|
||||
"ytlc",
|
||||
"ytuc",
|
||||
"ytas",
|
||||
"ytde"
|
||||
)
|
||||
|
||||
private val compoundItalicTokens = setOf("bolditalic", "boldital", "boldoblique", "boldobliq")
|
||||
private val compoundWeightTokens = mapOf(
|
||||
"extralight" to FontWeight.ExtraLight,
|
||||
"ultralight" to FontWeight.ExtraLight,
|
||||
"semibold" to FontWeight.SemiBold,
|
||||
"demibold" to FontWeight.SemiBold,
|
||||
"extrabold" to FontWeight.ExtraBold,
|
||||
"ultrabold" to FontWeight.ExtraBold
|
||||
)
|
||||
private val compoundVariableFontTokens = setOf("variablefont")
|
||||
private val compoundTokenWeightMap = compoundItalicTokens.associateWith { FontWeight.Bold } + compoundWeightTokens
|
||||
|
||||
private val singleVariantTokens = italicTokens + tokenWeightMap.keys + variableFontTokens
|
||||
private val compoundVariantTokens = compoundItalicTokens + compoundWeightTokens.keys + compoundVariableFontTokens
|
||||
|
|
@ -239,7 +239,9 @@ internal enum class NonReaderLibraryPrimaryAction {
|
|||
}
|
||||
|
||||
internal enum class NonReaderBookOverflowAction {
|
||||
ADD_TO_SHELF
|
||||
ADD_TO_SHELF,
|
||||
SAVE_ORIGINAL,
|
||||
SHARE_ORIGINAL
|
||||
}
|
||||
|
||||
internal fun visibleNonReaderLibraryTabs(
|
||||
|
|
@ -266,8 +268,14 @@ internal fun bookOverflowActionsForPlatform(
|
|||
platform: ReaderPlatform = ReaderPlatform.ANDROID
|
||||
): Set<NonReaderBookOverflowAction> {
|
||||
return when (platform) {
|
||||
ReaderPlatform.DESKTOP -> setOf(NonReaderBookOverflowAction.ADD_TO_SHELF)
|
||||
ReaderPlatform.ANDROID -> emptySet()
|
||||
ReaderPlatform.DESKTOP -> setOf(
|
||||
NonReaderBookOverflowAction.ADD_TO_SHELF,
|
||||
NonReaderBookOverflowAction.SAVE_ORIGINAL
|
||||
)
|
||||
ReaderPlatform.ANDROID -> setOf(
|
||||
NonReaderBookOverflowAction.SAVE_ORIGINAL,
|
||||
NonReaderBookOverflowAction.SHARE_ORIGINAL
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ import androidx.compose.material.icons.filled.MoreVert
|
|||
import androidx.compose.material.icons.filled.PushPin
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material.icons.filled.Sync
|
||||
import androidx.compose.material.icons.filled.Tag
|
||||
import androidx.compose.material3.AssistChip
|
||||
|
|
@ -165,6 +167,8 @@ fun SharedHomeScreen(
|
|||
onRemoveSelected: () -> Unit,
|
||||
onShowBookInfo: (BookItem) -> Unit = {},
|
||||
onEditBook: (BookItem) -> Unit = {},
|
||||
onSaveOriginalFile: (BookItem) -> Unit = {},
|
||||
onShareOriginalFile: (BookItem) -> Unit = {},
|
||||
onTagSelectedBooks: () -> Unit = {},
|
||||
onAddSelectedBooksToShelf: () -> Unit = {},
|
||||
onOpenTab: (BookItem) -> Unit = onOpenBook,
|
||||
|
|
@ -173,6 +177,7 @@ fun SharedHomeScreen(
|
|||
onRecentLimitChange: (Int) -> Unit = {},
|
||||
onTogglePinned: (BookItem) -> Unit = {},
|
||||
onOpenSettings: () -> Unit = {},
|
||||
platform: ReaderPlatform = ReaderPlatform.ANDROID,
|
||||
showActiveTabs: Boolean = true,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
|
@ -188,6 +193,16 @@ fun SharedHomeScreen(
|
|||
) {
|
||||
state.toNonReaderHomeLayoutModel()
|
||||
}
|
||||
val saveOriginalFileAction = if (NonReaderBookOverflowAction.SAVE_ORIGINAL in bookOverflowActionsForPlatform(platform)) {
|
||||
onSaveOriginalFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val shareOriginalFileAction = if (NonReaderBookOverflowAction.SHARE_ORIGINAL in bookOverflowActionsForPlatform(platform)) {
|
||||
onShareOriginalFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
NonReaderScreenScaffold(
|
||||
title = readerString("nav_home", "Home"),
|
||||
subtitle = readerString("desktop_home_subtitle", "Continue reading and recent books"),
|
||||
|
|
@ -268,6 +283,8 @@ fun SharedHomeScreen(
|
|||
onOpenBook = { onOpenBook(book) },
|
||||
onShowBookInfo = { onShowBookInfo(book) },
|
||||
onEditBook = { onEditBook(book) },
|
||||
onSaveOriginalFile = saveOriginalFileAction?.let { save -> { save(book) } },
|
||||
onShareOriginalFile = shareOriginalFileAction?.let { share -> { share(book) } },
|
||||
onTogglePinned = { onTogglePinned(book) }
|
||||
)
|
||||
}
|
||||
|
|
@ -294,6 +311,8 @@ fun SharedHomeScreen(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = saveOriginalFileAction,
|
||||
onShareOriginalFile = shareOriginalFileAction,
|
||||
onTogglePinned = onTogglePinned
|
||||
)
|
||||
}
|
||||
|
|
@ -309,6 +328,8 @@ fun SharedHomeScreen(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = saveOriginalFileAction,
|
||||
onShareOriginalFile = shareOriginalFileAction,
|
||||
onTogglePinned = onTogglePinned
|
||||
)
|
||||
}
|
||||
|
|
@ -331,6 +352,8 @@ fun SharedLibraryScreen(
|
|||
onRemoveSelected: () -> Unit,
|
||||
onShowBookInfo: (BookItem) -> Unit = {},
|
||||
onEditBook: (BookItem) -> Unit = {},
|
||||
onSaveOriginalFile: (BookItem) -> Unit = {},
|
||||
onShareOriginalFile: (BookItem) -> Unit = {},
|
||||
onCreateShelf: () -> Unit = {},
|
||||
onCreateShelfWithBooks: (String, Set<String>) -> Unit = { _, _ -> },
|
||||
onCreateSmartShelf: () -> Unit = {},
|
||||
|
|
@ -446,6 +469,8 @@ fun SharedLibraryScreen(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = onSaveOriginalFile,
|
||||
onShareOriginalFile = onShareOriginalFile,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onAddBooksToShelf = onAddBooksToShelf,
|
||||
onManageShelfBooks = onManageShelfBooks,
|
||||
|
|
@ -495,6 +520,8 @@ fun SharedLibraryScreen(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = onSaveOriginalFile,
|
||||
onShareOriginalFile = onShareOriginalFile,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onAddBooksToShelf = onAddBooksToShelf,
|
||||
onManageShelfBooks = onManageShelfBooks,
|
||||
|
|
@ -601,8 +628,11 @@ private fun ContinueReadingCard(
|
|||
onOpenBook: () -> Unit,
|
||||
onShowBookInfo: () -> Unit,
|
||||
onEditBook: () -> Unit,
|
||||
onSaveOriginalFile: (() -> Unit)?,
|
||||
onShareOriginalFile: (() -> Unit)?,
|
||||
onTogglePinned: () -> Unit
|
||||
) {
|
||||
val canUseOriginalFileActions = !book.isOpdsStream() && book.path != null
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(SharedUiTokens.surfaceRadius),
|
||||
|
|
@ -643,6 +673,16 @@ private fun ContinueReadingCard(
|
|||
IconButton(onClick = onEditBook) {
|
||||
Icon(Icons.Default.Edit, contentDescription = readerString("action_edit", "Edit"))
|
||||
}
|
||||
if (canUseOriginalFileActions && onSaveOriginalFile != null) {
|
||||
IconButton(onClick = onSaveOriginalFile) {
|
||||
Icon(Icons.Default.Save, contentDescription = readerString("action_save_copy_to_device", "Save copy to device"))
|
||||
}
|
||||
}
|
||||
if (canUseOriginalFileActions && onShareOriginalFile != null) {
|
||||
IconButton(onClick = onShareOriginalFile) {
|
||||
Icon(Icons.Default.Share, contentDescription = readerString("action_share", "Share"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -659,6 +699,8 @@ private fun HomeBookShelf(
|
|||
onToggleSelection: (String) -> Unit,
|
||||
onShowBookInfo: (BookItem) -> Unit,
|
||||
onEditBook: (BookItem) -> Unit,
|
||||
onSaveOriginalFile: ((BookItem) -> Unit)?,
|
||||
onShareOriginalFile: ((BookItem) -> Unit)?,
|
||||
onTogglePinned: (BookItem) -> Unit
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
|
|
@ -674,6 +716,8 @@ private fun HomeBookShelf(
|
|||
onToggleSelection = { onToggleSelection(book.id) },
|
||||
onShowInfo = { onShowBookInfo(book) },
|
||||
onEdit = { onEditBook(book) },
|
||||
onSaveOriginalFile = onSaveOriginalFile?.let { save -> { save(book) } },
|
||||
onShareOriginalFile = onShareOriginalFile?.let { share -> { share(book) } },
|
||||
onTogglePinned = { onTogglePinned(book) },
|
||||
modifier = Modifier.width(168.dp)
|
||||
)
|
||||
|
|
@ -1226,6 +1270,8 @@ private fun LibraryContent(
|
|||
onToggleSelection: (String) -> Unit,
|
||||
onShowBookInfo: (BookItem) -> Unit,
|
||||
onEditBook: (BookItem) -> Unit,
|
||||
onSaveOriginalFile: (BookItem) -> Unit,
|
||||
onShareOriginalFile: (BookItem) -> Unit,
|
||||
onTogglePinned: (BookItem) -> Unit,
|
||||
onAddBooksToShelf: (Set<String>) -> Unit,
|
||||
onManageShelfBooks: ((Shelf) -> Unit)?,
|
||||
|
|
@ -1262,6 +1308,16 @@ private fun LibraryContent(
|
|||
} else {
|
||||
null
|
||||
}
|
||||
val saveOriginalFileAction = if (NonReaderBookOverflowAction.SAVE_ORIGINAL in bookOverflowActionsForPlatform(platform)) {
|
||||
onSaveOriginalFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val shareOriginalFileAction = if (NonReaderBookOverflowAction.SHARE_ORIGINAL in bookOverflowActionsForPlatform(platform)) {
|
||||
onShareOriginalFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val manageShelfBooksAction = if (platform == ReaderPlatform.DESKTOP) onManageShelfBooks else null
|
||||
val showNewShelfPrimaryAction = NonReaderLibraryPrimaryAction.NEW_SHELF in
|
||||
primaryLibraryActionsForTab(selectedTab, platform)
|
||||
|
|
@ -1314,6 +1370,8 @@ private fun LibraryContent(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = saveOriginalFileAction,
|
||||
onShareOriginalFile = shareOriginalFileAction,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onAddToShelf = addToShelfFromBookAction?.let { addToShelf -> { book -> addToShelf(setOf(book.id)) } },
|
||||
modifier = Modifier.weight(1f)
|
||||
|
|
@ -1347,6 +1405,8 @@ private fun LibraryContent(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = saveOriginalFileAction,
|
||||
onShareOriginalFile = shareOriginalFileAction,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onAddBooksToShelf = addToShelfFromBookAction,
|
||||
onManageShelfBooks = manageShelfBooksAction,
|
||||
|
|
@ -1370,6 +1430,8 @@ private fun LibraryContent(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = saveOriginalFileAction,
|
||||
onShareOriginalFile = shareOriginalFileAction,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onAddBooksToShelf = addToShelfFromBookAction,
|
||||
onRenameShelf = onRenameShelf,
|
||||
|
|
@ -1387,6 +1449,8 @@ private fun LibraryContent(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = saveOriginalFileAction,
|
||||
onShareOriginalFile = shareOriginalFileAction,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onAddBooksToShelf = addToShelfFromBookAction,
|
||||
emptyTitle = readerString("desktop_no_tags_yet", "No tags yet"),
|
||||
|
|
@ -1409,6 +1473,8 @@ private fun LibraryContent(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = saveOriginalFileAction,
|
||||
onShareOriginalFile = shareOriginalFileAction,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onAddBooksToShelf = addToShelfFromBookAction,
|
||||
onOpenShelf = { shelf -> onStateChange(state.copy(viewingShelfId = shelf.id)) },
|
||||
|
|
@ -1431,6 +1497,8 @@ private fun LibraryContent(
|
|||
onToggleSelection = onToggleSelection,
|
||||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onSaveOriginalFile = saveOriginalFileAction,
|
||||
onShareOriginalFile = shareOriginalFileAction,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onAddBooksToShelf = addToShelfFromBookAction,
|
||||
onRemoveFolder = onRemoveFolder,
|
||||
|
|
@ -1690,6 +1758,8 @@ private fun BookGrid(
|
|||
onShowBookInfo: (BookItem) -> Unit,
|
||||
onEditBook: (BookItem) -> Unit,
|
||||
onTogglePinned: (BookItem) -> Unit,
|
||||
onSaveOriginalFile: ((BookItem) -> Unit)? = null,
|
||||
onShareOriginalFile: ((BookItem) -> Unit)? = null,
|
||||
onAddToShelf: ((BookItem) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
|
@ -1710,6 +1780,8 @@ private fun BookGrid(
|
|||
onShowInfo = { onShowBookInfo(book) },
|
||||
onEdit = { onEditBook(book) },
|
||||
onTogglePinned = { onTogglePinned(book) },
|
||||
onSaveOriginalFile = onSaveOriginalFile?.let { save -> { save(book) } },
|
||||
onShareOriginalFile = onShareOriginalFile?.let { share -> { share(book) } },
|
||||
onAddToShelf = onAddToShelf?.let { addToShelf -> { addToShelf(book) } }
|
||||
)
|
||||
}
|
||||
|
|
@ -1733,6 +1805,8 @@ private fun BookGrid(
|
|||
onShowInfo = { onShowBookInfo(book) },
|
||||
onEdit = { onEditBook(book) },
|
||||
onTogglePinned = { onTogglePinned(book) },
|
||||
onSaveOriginalFile = onSaveOriginalFile?.let { save -> { save(book) } },
|
||||
onShareOriginalFile = onShareOriginalFile?.let { share -> { share(book) } },
|
||||
onAddToShelf = onAddToShelf?.let { addToShelf -> { addToShelf(book) } }
|
||||
)
|
||||
}
|
||||
|
|
@ -1752,6 +1826,8 @@ private fun BookTile(
|
|||
onShowInfo: () -> Unit,
|
||||
onEdit: () -> Unit,
|
||||
onTogglePinned: () -> Unit,
|
||||
onSaveOriginalFile: (() -> Unit)? = null,
|
||||
onShareOriginalFile: (() -> Unit)? = null,
|
||||
onAddToShelf: (() -> Unit)? = null,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
|
@ -1803,6 +1879,8 @@ private fun BookTile(
|
|||
onShowInfo = onShowInfo,
|
||||
onEdit = onEdit,
|
||||
onToggleSelection = onToggleSelection,
|
||||
onSaveOriginalFile = onSaveOriginalFile.takeIf { !book.isOpdsStream() && book.path != null },
|
||||
onShareOriginalFile = onShareOriginalFile.takeIf { !book.isOpdsStream() && book.path != null },
|
||||
onAddToShelf = onAddToShelf
|
||||
)
|
||||
}
|
||||
|
|
@ -1839,6 +1917,8 @@ private fun BookListItem(
|
|||
onShowInfo: () -> Unit,
|
||||
onEdit: () -> Unit,
|
||||
onTogglePinned: () -> Unit,
|
||||
onSaveOriginalFile: (() -> Unit)? = null,
|
||||
onShareOriginalFile: (() -> Unit)? = null,
|
||||
onAddToShelf: (() -> Unit)? = null
|
||||
) {
|
||||
var menuExpanded by remember { mutableStateOf(false) }
|
||||
|
|
@ -1881,6 +1961,8 @@ private fun BookListItem(
|
|||
onShowInfo = onShowInfo,
|
||||
onEdit = onEdit,
|
||||
onToggleSelection = onToggleSelection,
|
||||
onSaveOriginalFile = onSaveOriginalFile.takeIf { !book.isOpdsStream() && book.path != null },
|
||||
onShareOriginalFile = onShareOriginalFile.takeIf { !book.isOpdsStream() && book.path != null },
|
||||
onAddToShelf = onAddToShelf
|
||||
)
|
||||
}
|
||||
|
|
@ -1898,6 +1980,8 @@ private fun BookActionMenu(
|
|||
onShowInfo: () -> Unit,
|
||||
onEdit: () -> Unit,
|
||||
onToggleSelection: () -> Unit,
|
||||
onSaveOriginalFile: (() -> Unit)? = null,
|
||||
onShareOriginalFile: (() -> Unit)? = null,
|
||||
onAddToShelf: (() -> Unit)? = null
|
||||
) {
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
|
||||
|
|
@ -1925,6 +2009,26 @@ private fun BookActionMenu(
|
|||
onEdit()
|
||||
}
|
||||
)
|
||||
if (onSaveOriginalFile != null) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = { Icon(Icons.Default.Save, contentDescription = null) },
|
||||
text = { Text(readerString("action_save_copy_to_device", "Save copy to device")) },
|
||||
onClick = {
|
||||
onDismiss()
|
||||
onSaveOriginalFile()
|
||||
}
|
||||
)
|
||||
}
|
||||
if (onShareOriginalFile != null) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = { Icon(Icons.Default.Share, contentDescription = null) },
|
||||
text = { Text(readerString("action_share", "Share")) },
|
||||
onClick = {
|
||||
onDismiss()
|
||||
onShareOriginalFile()
|
||||
}
|
||||
)
|
||||
}
|
||||
if (onAddToShelf != null) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = { Icon(Icons.Default.Folder, contentDescription = null) },
|
||||
|
|
@ -2112,6 +2216,8 @@ private fun ShelfCollection(
|
|||
onShowBookInfo: (BookItem) -> Unit,
|
||||
onEditBook: (BookItem) -> Unit,
|
||||
onTogglePinned: (BookItem) -> Unit,
|
||||
onSaveOriginalFile: ((BookItem) -> Unit)? = null,
|
||||
onShareOriginalFile: ((BookItem) -> Unit)? = null,
|
||||
onAddBooksToShelf: ((Set<String>) -> Unit)? = null,
|
||||
onManageShelfBooks: ((Shelf) -> Unit)? = null,
|
||||
onRenameShelf: (Shelf) -> Unit = {},
|
||||
|
|
@ -2151,6 +2257,8 @@ private fun ShelfCollection(
|
|||
onShowBookInfo = onShowBookInfo,
|
||||
onEditBook = onEditBook,
|
||||
onTogglePinned = onTogglePinned,
|
||||
onSaveOriginalFile = onSaveOriginalFile,
|
||||
onShareOriginalFile = onShareOriginalFile,
|
||||
onAddBooksToShelf = onAddBooksToShelf,
|
||||
onManageShelfBooks = onManageShelfBooks,
|
||||
onRenameShelf = onRenameShelf,
|
||||
|
|
@ -2172,6 +2280,8 @@ private fun ShelfSection(
|
|||
onShowBookInfo: (BookItem) -> Unit,
|
||||
onEditBook: (BookItem) -> Unit,
|
||||
onTogglePinned: (BookItem) -> Unit,
|
||||
onSaveOriginalFile: ((BookItem) -> Unit)?,
|
||||
onShareOriginalFile: ((BookItem) -> Unit)?,
|
||||
onAddBooksToShelf: ((Set<String>) -> Unit)?,
|
||||
onManageShelfBooks: ((Shelf) -> Unit)?,
|
||||
onRenameShelf: (Shelf) -> Unit,
|
||||
|
|
@ -2258,6 +2368,8 @@ private fun ShelfSection(
|
|||
onShowInfo = { onShowBookInfo(book) },
|
||||
onEdit = { onEditBook(book) },
|
||||
onTogglePinned = { onTogglePinned(book) },
|
||||
onSaveOriginalFile = onSaveOriginalFile?.let { save -> { save(book) } },
|
||||
onShareOriginalFile = onShareOriginalFile?.let { share -> { share(book) } },
|
||||
onAddToShelf = onAddBooksToShelf?.let { addToShelf -> { addToShelf(setOf(book.id)) } },
|
||||
modifier = Modifier.width(148.dp)
|
||||
)
|
||||
|
|
@ -2279,6 +2391,8 @@ private fun FolderShelfDetail(
|
|||
onShowBookInfo: (BookItem) -> Unit,
|
||||
onEditBook: (BookItem) -> Unit,
|
||||
onTogglePinned: (BookItem) -> Unit,
|
||||
onSaveOriginalFile: ((BookItem) -> Unit)? = null,
|
||||
onShareOriginalFile: ((BookItem) -> Unit)? = null,
|
||||
onAddBooksToShelf: ((Set<String>) -> Unit)? = null,
|
||||
onOpenShelf: (Shelf) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
|
|
@ -2345,6 +2459,8 @@ private fun FolderShelfDetail(
|
|||
onShowInfo = { onShowBookInfo(book) },
|
||||
onEdit = { onEditBook(book) },
|
||||
onTogglePinned = { onTogglePinned(book) },
|
||||
onSaveOriginalFile = onSaveOriginalFile?.let { save -> { save(book) } },
|
||||
onShareOriginalFile = onShareOriginalFile?.let { share -> { share(book) } },
|
||||
onAddToShelf = onAddBooksToShelf?.let { addToShelf -> { addToShelf(setOf(book.id)) } }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1663,13 +1663,9 @@ private fun List<ReaderPage>.findSharedNativeVerticalPageIndexForBlock(block: Se
|
|||
return firstOrNull()?.pageIndex
|
||||
}
|
||||
|
||||
private fun List<SharedNativeVerticalFlowItem>.sharedNativeVerticalItemIndexForLocator(
|
||||
internal fun List<SharedNativeVerticalFlowItem>.sharedNativeVerticalItemIndexForLocator(
|
||||
locator: ReaderLocator
|
||||
): Int? {
|
||||
locator.pageIndex?.let { pageIndex ->
|
||||
val samePage = indexOfFirst { item -> item.page.pageIndex == pageIndex }
|
||||
if (samePage >= 0) return samePage
|
||||
}
|
||||
val chapterIndex = locator.chapterIndex
|
||||
if (chapterIndex != null) {
|
||||
locator.blockIndex?.let { blockIndex ->
|
||||
|
|
@ -1687,6 +1683,12 @@ private fun List<SharedNativeVerticalFlowItem>.sharedNativeVerticalItemIndexForL
|
|||
}
|
||||
if (sameOffset >= 0) return sameOffset
|
||||
}
|
||||
}
|
||||
locator.pageIndex?.let { pageIndex ->
|
||||
val samePage = indexOfFirst { item -> item.page.pageIndex == pageIndex }
|
||||
if (samePage >= 0) return samePage
|
||||
}
|
||||
if (chapterIndex != null) {
|
||||
val sameChapter = indexOfFirst { item -> item.page.chapterIndex == chapterIndex }
|
||||
if (sameChapter >= 0) return sameChapter
|
||||
}
|
||||
|
|
@ -3170,7 +3172,7 @@ private fun SemanticTextBlock.renderedTextStyle(
|
|||
).takeIf { it.isSpecified } ?: foreground,
|
||||
fontSize = fontSize,
|
||||
lineHeight = lineHeight,
|
||||
fontFamily = fallbackFontFamily,
|
||||
fontFamily = style.spanStyle.fontFamily ?: fallbackFontFamily,
|
||||
fontWeight = fontWeight
|
||||
?: style.spanStyle.fontWeight
|
||||
?: if (this is SemanticHeader) FontWeight.Bold else MaterialTheme.typography.bodyLarge.fontWeight,
|
||||
|
|
|
|||
|
|
@ -117,6 +117,9 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.zIndex
|
||||
import com.aryan.reader.shared.BuiltInReaderThemes
|
||||
import com.aryan.reader.shared.CustomFontItem
|
||||
import com.aryan.reader.shared.fontFaceSummary
|
||||
import com.aryan.reader.shared.groupByFamily
|
||||
import com.aryan.reader.shared.hasVariableWeightFace
|
||||
import com.aryan.reader.shared.HighlightColor
|
||||
import com.aryan.reader.shared.PageInfoMode
|
||||
import com.aryan.reader.shared.PageInfoPosition
|
||||
|
|
@ -1740,28 +1743,46 @@ fun SharedReaderFormatControls(
|
|||
}
|
||||
}
|
||||
|
||||
val activeCustomFonts = customFonts.filterNot { it.isDeleted }.sortedBy { it.displayName.lowercase() }
|
||||
if (activeCustomFonts.isNotEmpty()) {
|
||||
val activeCustomFontFamilies = customFonts.filterNot { it.isDeleted }.groupByFamily()
|
||||
if (activeCustomFontFamilies.isNotEmpty()) {
|
||||
Text(
|
||||
readerString("desktop_imported_fonts", "Imported fonts"),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
SharedReaderChoiceRow {
|
||||
activeCustomFonts.forEach { font ->
|
||||
activeCustomFontFamilies.forEach { family ->
|
||||
val isSelected = family.variants.any { it.font.path == settings.customFontPath }
|
||||
FilterChip(
|
||||
selected = settings.customFontPath == font.path,
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
val baseFont = family.variants.firstOrNull { it.variant?.weight == FontWeight.Normal && it.variant?.style == androidx.compose.ui.text.font.FontStyle.Normal }?.font ?: family.variants.first().font
|
||||
onReaderAction(
|
||||
ReaderAction.SettingsChanged(
|
||||
settings.copy(
|
||||
fontFamily = font.displayName,
|
||||
customFontPath = font.path
|
||||
fontFamily = family.familyName,
|
||||
customFontPath = baseFont.path
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
label = { Text(font.displayName, maxLines = 1, overflow = TextOverflow.Ellipsis) }
|
||||
label = {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(family.familyName, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
val variantsStr = buildString {
|
||||
append(family.fontFaceSummary())
|
||||
if (family.hasVariableWeightFace()) append(" - Variable weight")
|
||||
}
|
||||
if (variantsStr.isNotBlank()) {
|
||||
Text(
|
||||
"($variantsStr)",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue