Export: avoid running preparation when it's useless
This commit is contained in:
@@ -185,7 +185,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
uiState = exportUiState,
|
uiState = exportUiState,
|
||||||
currentDocument = document,
|
currentDocument = document,
|
||||||
pdfActions = ExportActions(
|
pdfActions = ExportActions(
|
||||||
initializeExportScreen = exportViewModel::initializeExportScreen,
|
prepareExportIfNeeded = exportViewModel::prepareExportIfNeeded,
|
||||||
setFilename = exportViewModel::setFilename,
|
setFilename = exportViewModel::setFilename,
|
||||||
share = { share(exportViewModel.applyRenaming(), exportViewModel) },
|
share = { share(exportViewModel.applyRenaming(), exportViewModel) },
|
||||||
save = { exportViewModel.onSaveClicked() },
|
save = { exportViewModel.onSaveClicked() },
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ fun ExportScreenWrapper(
|
|||||||
val showConfirmationDialog = rememberSaveable { mutableStateOf(false) }
|
val showConfirmationDialog = rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
pdfActions.initializeExportScreen()
|
pdfActions.prepareExportIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
val onFilenameChange = { newName:String ->
|
val onFilenameChange = { newName:String ->
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import androidx.core.net.toUri
|
|||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@@ -44,6 +46,7 @@ import org.fairscan.app.RecentDocument
|
|||||||
import org.fairscan.app.data.FileManager
|
import org.fairscan.app.data.FileManager
|
||||||
import org.fairscan.app.data.ImageRepository
|
import org.fairscan.app.data.ImageRepository
|
||||||
import org.fairscan.app.domain.ExportQuality
|
import org.fairscan.app.domain.ExportQuality
|
||||||
|
import org.fairscan.app.domain.PageViewKey
|
||||||
import org.fairscan.app.domain.jpegsForExport
|
import org.fairscan.app.domain.jpegsForExport
|
||||||
import org.fairscan.app.ui.screens.settings.ExportFormat
|
import org.fairscan.app.ui.screens.settings.ExportFormat
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -52,6 +55,7 @@ import java.io.IOException
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.CancellationException
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
@@ -85,8 +89,8 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
private val _uiState = MutableStateFlow(ExportUiState())
|
private val _uiState = MutableStateFlow(ExportUiState())
|
||||||
val uiState: StateFlow<ExportUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<ExportUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private var lastPreparationKey: ExportPreparationKey? = null
|
||||||
private var preparationJob: Job? = null
|
private var preparationJob: Job? = null
|
||||||
private var exportFormat = ExportFormat.PDF
|
|
||||||
|
|
||||||
fun setFilename(name: String) {
|
fun setFilename(name: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
@@ -114,15 +118,30 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initializeExportScreen() {
|
private fun currentPageKeys(): ImmutableList<PageViewKey> =
|
||||||
preparationJob?.cancel()
|
imageRepository.pages().map {
|
||||||
_uiState.update { ExportUiState(filename = it.filename) }
|
PageViewKey(it.id, it.manualRotation)
|
||||||
|
}.toImmutableList()
|
||||||
|
|
||||||
|
fun prepareExportIfNeeded() {
|
||||||
ensureValidFilename()
|
ensureValidFilename()
|
||||||
|
|
||||||
preparationJob = viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val exportQuality = settingsRepository.exportQuality.first()
|
val exportQuality = settingsRepository.exportQuality.first()
|
||||||
exportFormat = settingsRepository.exportFormat.first()
|
val exportFormat = settingsRepository.exportFormat.first()
|
||||||
_uiState.update { it.copy(format = exportFormat, isGenerating = true) }
|
|
||||||
|
val key = ExportPreparationKey(currentPageKeys(), exportFormat, exportQuality)
|
||||||
|
if (key == lastPreparationKey) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPreparationKey = key
|
||||||
|
preparationJob?.cancel()
|
||||||
|
|
||||||
|
preparationJob = launch {
|
||||||
|
_uiState.update {
|
||||||
|
ExportUiState(filename = it.filename, format = exportFormat, isGenerating = true)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val t1 = System.currentTimeMillis()
|
val t1 = System.currentTimeMillis()
|
||||||
val result = if (exportFormat == ExportFormat.JPEG) {
|
val result = if (exportFormat == ExportFormat.JPEG) {
|
||||||
@@ -130,20 +149,21 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
} else {
|
} else {
|
||||||
generatePdf(exportQuality)
|
generatePdf(exportQuality)
|
||||||
}
|
}
|
||||||
_uiState.update {
|
_uiState.update { it.copy(result = result) }
|
||||||
it.copy(isGenerating = false, result = result)
|
|
||||||
}
|
|
||||||
val t2 = System.currentTimeMillis()
|
val t2 = System.currentTimeMillis()
|
||||||
val pageCount = result.pageCount
|
val pageCount = result.pageCount
|
||||||
Log.i("Export", "Generation: $pageCount pages, $exportQuality, ${t2 - t1} ms")
|
Log.i("Export", "Generation: $pageCount pages, $exportQuality, ${t2 - t1} ms")
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
// Preparation cancelled: do nothing
|
||||||
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val message = "Failed to prepare $exportFormat export"
|
val message = "Failed to prepare $exportFormat export"
|
||||||
logger.e("FairScan", message, e)
|
logger.e("FairScan", message, e)
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(error = ExportError.OnPrepare(message, e))
|
||||||
isGenerating = false,
|
}
|
||||||
error = ExportError.OnPrepare(message, e),
|
} finally {
|
||||||
)
|
_uiState.update { it.copy(isGenerating = false) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,12 +236,13 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
fun onRequestSave(context: Context) {
|
fun onRequestSave(context: Context) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_uiState.update {it.copy(isSaving = true, error = null, savedBundle = null) }
|
_uiState.update {it.copy(isSaving = true, error = null, savedBundle = null) }
|
||||||
|
val exportFormat = uiState.value.format
|
||||||
val saveDir = saveDir(context)
|
val saveDir = saveDir(context)
|
||||||
try {
|
try {
|
||||||
// Must not run on the main thread: some SAF providers (e.g. Nextcloud)
|
// Must not run on the main thread: some SAF providers (e.g. Nextcloud)
|
||||||
// may perform network I/O
|
// may perform network I/O
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
save(context, saveDir)
|
save(context, saveDir, exportFormat)
|
||||||
}
|
}
|
||||||
} catch (e: MissingExportDirPermissionException) {
|
} catch (e: MissingExportDirPermissionException) {
|
||||||
logger.e("FairScan", "Missing export dir permission", e)
|
logger.e("FairScan", "Missing export dir permission", e)
|
||||||
@@ -246,7 +267,7 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
return SaveDir(uri, name)
|
return SaveDir(uri, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun save(context: Context, saveDir: SaveDir?) {
|
private suspend fun save(context: Context, saveDir: SaveDir?, exportFormat: ExportFormat) {
|
||||||
val result = applyRenaming() ?: return
|
val result = applyRenaming() ?: return
|
||||||
val savedItems = mutableListOf<SavedItem>()
|
val savedItems = mutableListOf<SavedItem>()
|
||||||
val filesForMediaScan = mutableListOf<File>()
|
val filesForMediaScan = mutableListOf<File>()
|
||||||
@@ -389,6 +410,12 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ExportPreparationKey(
|
||||||
|
val pages: ImmutableList<PageViewKey>,
|
||||||
|
val format: ExportFormat,
|
||||||
|
val quality: ExportQuality
|
||||||
|
)
|
||||||
|
|
||||||
sealed class ExportResult {
|
sealed class ExportResult {
|
||||||
abstract val files: List<File>
|
abstract val files: List<File>
|
||||||
abstract val sizeInBytes: Long
|
abstract val sizeInBytes: Long
|
||||||
@@ -415,7 +442,7 @@ sealed class ExportResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class ExportActions(
|
data class ExportActions(
|
||||||
val initializeExportScreen: () -> Unit,
|
val prepareExportIfNeeded: () -> Unit,
|
||||||
val setFilename: (String) -> Unit,
|
val setFilename: (String) -> Unit,
|
||||||
val share: () -> Unit,
|
val share: () -> Unit,
|
||||||
val save: () -> Unit,
|
val save: () -> Unit,
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ private fun SettingsContent(
|
|||||||
onExportQualityChanged: (ExportQuality) -> Unit,
|
onExportQualityChanged: (ExportQuality) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
println(uiState)
|
|
||||||
val (folderLabel, folderLabelColor) = when {
|
val (folderLabel, folderLabelColor) = when {
|
||||||
uiState.exportDirUri == null ->
|
uiState.exportDirUri == null ->
|
||||||
stringResource(R.string.download_dirname) to
|
stringResource(R.string.download_dirname) to
|
||||||
|
|||||||
Reference in New Issue
Block a user