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
|
|
@ -133,6 +133,7 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
|
|
@ -271,6 +272,36 @@ fun LibraryScreen(
|
|||
var showDeleteShelvesDialog by remember { mutableStateOf(false) }
|
||||
var showInfoDialog by remember { mutableStateOf(false) }
|
||||
var itemForInfoDialog by remember { mutableStateOf<RecentFileItem?>(null) }
|
||||
var pendingSaveOriginalItem by remember { mutableStateOf<RecentFileItem?>(null) }
|
||||
|
||||
val saveOriginalLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/octet-stream")
|
||||
) { uri ->
|
||||
val item = pendingSaveOriginalItem
|
||||
pendingSaveOriginalItem = null
|
||||
if (uri != null && item?.uriString != null) {
|
||||
viewModel.saveOriginalFile(item.uriString.toUri(), uri)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveOriginalItem(item: RecentFileItem) {
|
||||
if (!item.canExportOriginalFile()) return
|
||||
pendingSaveOriginalItem = item
|
||||
saveOriginalLauncher.launch(item.suggestedOriginalFileName())
|
||||
}
|
||||
|
||||
fun shareOriginalItem(item: RecentFileItem) {
|
||||
val uriString = item.uriString ?: return
|
||||
if (!item.canExportOriginalFile()) return
|
||||
scope.launch {
|
||||
viewModel.shareOriginalFile(
|
||||
activityContext = context,
|
||||
sourceUri = uriString.toUri(),
|
||||
fileType = item.type,
|
||||
filename = item.suggestedOriginalFileName()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = isContextualModeActive) {
|
||||
viewModel.clearContextualAction()
|
||||
|
|
@ -317,6 +348,12 @@ fun LibraryScreen(
|
|||
showInfoDialog = true
|
||||
}
|
||||
},
|
||||
onSaveClick = selectedItems.singleOrNull()
|
||||
?.takeIf { it.canExportOriginalFile() }
|
||||
?.let { item -> { saveOriginalItem(item) } },
|
||||
onShareClick = selectedItems.singleOrNull()
|
||||
?.takeIf { it.canExportOriginalFile() }
|
||||
?.let { item -> { shareOriginalItem(item) } },
|
||||
onDeleteClick = { showDeleteConfirmDialog = true },
|
||||
onSelectAllClick = { viewModel.selectAllLibraryFiles() },
|
||||
onShelfClick = viewModel::onShelfClick,
|
||||
|
|
@ -397,28 +434,17 @@ fun LibraryScreen(
|
|||
)
|
||||
}
|
||||
|
||||
itemForInfoDialog?.let { item ->
|
||||
if (showInfoDialog) {
|
||||
FileInfoDialog(
|
||||
item = item,
|
||||
usePdfFileNameAsDisplayName = uiState.usePdfFileNameAsDisplayName,
|
||||
onDismiss = {
|
||||
showInfoDialog = false
|
||||
itemForInfoDialog = null
|
||||
},
|
||||
onSaveMetadata = { metadata ->
|
||||
viewModel.updateBookMetadata(item.bookId, metadata)
|
||||
},
|
||||
onSaveDisplayName = { name ->
|
||||
viewModel.updateCustomName(item.bookId, name)
|
||||
},
|
||||
onRestoreMetadata = {
|
||||
viewModel.restoreOriginalBookMetadata(item.bookId)
|
||||
},
|
||||
onOpenTags = { viewModel.openTagSelection(setOf(item.bookId)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
HydratedFileInfoDialog(
|
||||
item = itemForInfoDialog,
|
||||
isVisible = showInfoDialog,
|
||||
uiState = uiState,
|
||||
viewModel = viewModel,
|
||||
onDismiss = {
|
||||
showInfoDialog = false
|
||||
itemForInfoDialog = null
|
||||
},
|
||||
onOpenTags = { bookId -> viewModel.openTagSelection(setOf(bookId)) }
|
||||
)
|
||||
CustomTopBanner(bannerMessage = uiState.bannerMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -436,10 +462,42 @@ fun ShelfScreen(
|
|||
val sortOrder = uiState.sortOrder
|
||||
val showRenameDialogFor = uiState.showRenameShelfDialogFor
|
||||
val showDeleteDialogFor = uiState.showDeleteShelfDialogFor
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var showRemoveFromShelfDialog by remember { mutableStateOf(false) }
|
||||
var showInfoDialog by remember { mutableStateOf(false) }
|
||||
var itemForInfoDialog by remember { mutableStateOf<RecentFileItem?>(null) }
|
||||
var pendingSaveOriginalItem by remember { mutableStateOf<RecentFileItem?>(null) }
|
||||
|
||||
val saveOriginalLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/octet-stream")
|
||||
) { uri ->
|
||||
val item = pendingSaveOriginalItem
|
||||
pendingSaveOriginalItem = null
|
||||
if (uri != null && item?.uriString != null) {
|
||||
viewModel.saveOriginalFile(item.uriString.toUri(), uri)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveOriginalItem(item: RecentFileItem) {
|
||||
if (!item.canExportOriginalFile()) return
|
||||
pendingSaveOriginalItem = item
|
||||
saveOriginalLauncher.launch(item.suggestedOriginalFileName())
|
||||
}
|
||||
|
||||
fun shareOriginalItem(item: RecentFileItem) {
|
||||
val uriString = item.uriString ?: return
|
||||
if (!item.canExportOriginalFile()) return
|
||||
scope.launch {
|
||||
viewModel.shareOriginalFile(
|
||||
activityContext = context,
|
||||
sourceUri = uriString.toUri(),
|
||||
fileType = item.type,
|
||||
filename = item.suggestedOriginalFileName()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = true) {
|
||||
when {
|
||||
|
|
@ -491,6 +549,12 @@ fun ShelfScreen(
|
|||
showInfoDialog = true
|
||||
}
|
||||
},
|
||||
onSaveClick = selectedItems.singleOrNull()
|
||||
?.takeIf { it.canExportOriginalFile() }
|
||||
?.let { item -> { saveOriginalItem(item) } },
|
||||
onShareClick = selectedItems.singleOrNull()
|
||||
?.takeIf { it.canExportOriginalFile() }
|
||||
?.let { item -> { shareOriginalItem(item) } },
|
||||
onDeleteClick = { showRemoveFromShelfDialog = true },
|
||||
onRenameShelf = { viewModel.showRenameShelfDialog(currentShelf.id) },
|
||||
onDeleteShelf = { viewModel.showDeleteShelfDialog(currentShelf.id) },
|
||||
|
|
@ -531,19 +595,14 @@ fun ShelfScreen(
|
|||
)
|
||||
}
|
||||
|
||||
itemForInfoDialog?.let { item ->
|
||||
if (showInfoDialog) {
|
||||
FileInfoDialog(
|
||||
item = item,
|
||||
usePdfFileNameAsDisplayName = uiState.usePdfFileNameAsDisplayName,
|
||||
onDismiss = { showInfoDialog = false; itemForInfoDialog = null },
|
||||
onSaveMetadata = { metadata -> viewModel.updateBookMetadata(item.bookId, metadata) },
|
||||
onSaveDisplayName = { name -> viewModel.updateCustomName(item.bookId, name) },
|
||||
onRestoreMetadata = { viewModel.restoreOriginalBookMetadata(item.bookId) },
|
||||
onOpenTags = { viewModel.openTagSelection(setOf(item.bookId)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
HydratedFileInfoDialog(
|
||||
item = itemForInfoDialog,
|
||||
isVisible = showInfoDialog,
|
||||
uiState = uiState,
|
||||
viewModel = viewModel,
|
||||
onDismiss = { showInfoDialog = false; itemForInfoDialog = null },
|
||||
onOpenTags = { bookId -> viewModel.openTagSelection(setOf(bookId)) }
|
||||
)
|
||||
CustomTopBanner(bannerMessage = uiState.bannerMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -578,6 +637,8 @@ fun LibraryScreenContent(
|
|||
onItemClick: (RecentFileItem) -> Unit,
|
||||
onItemLongClick: (RecentFileItem) -> Unit,
|
||||
onInfoClick: () -> Unit,
|
||||
onSaveClick: (() -> Unit)?,
|
||||
onShareClick: (() -> Unit)?,
|
||||
onDeleteClick: () -> Unit,
|
||||
onSelectAllClick: () -> Unit,
|
||||
onShelfClick: (Shelf) -> Unit,
|
||||
|
|
@ -640,6 +701,8 @@ fun LibraryScreenContent(
|
|||
onTagClick = onTagClick,
|
||||
onPinClick = onPinClick,
|
||||
onInfoClick = onInfoClick,
|
||||
onSaveClick = onSaveClick,
|
||||
onShareClick = onShareClick,
|
||||
onDeleteClick = onDeleteClick,
|
||||
onSelectAllClick = onSelectAllClick
|
||||
)
|
||||
|
|
@ -1046,6 +1109,8 @@ private fun ShelfDetailScreen(
|
|||
onClearSelection: () -> Unit,
|
||||
onTagClick: () -> Unit,
|
||||
onInfoClick: () -> Unit,
|
||||
onSaveClick: (() -> Unit)?,
|
||||
onShareClick: (() -> Unit)?,
|
||||
onDeleteClick: () -> Unit,
|
||||
onRenameShelf: () -> Unit,
|
||||
onDeleteShelf: () -> Unit,
|
||||
|
|
@ -1128,6 +1193,8 @@ private fun ShelfDetailScreen(
|
|||
onNavIconClick = onClearSelection,
|
||||
onTagClick = onTagClick,
|
||||
onInfoClick = onInfoClick,
|
||||
onSaveClick = onSaveClick,
|
||||
onShareClick = onShareClick,
|
||||
onDeleteClick = onDeleteClick
|
||||
)
|
||||
} else if (isSearchActive) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue