Export: avoid silencing issues in applyRenaming
This commit is contained in:
@@ -187,7 +187,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
pdfActions = ExportActions(
|
pdfActions = ExportActions(
|
||||||
prepareExportIfNeeded = exportViewModel::prepareExportIfNeeded,
|
prepareExportIfNeeded = exportViewModel::prepareExportIfNeeded,
|
||||||
setFilename = exportViewModel::setFilename,
|
setFilename = exportViewModel::setFilename,
|
||||||
share = { share(exportViewModel.applyRenaming(), exportViewModel) },
|
share = { exportViewModel.onShareClicked() },
|
||||||
save = { exportViewModel.onSaveClicked() },
|
save = { exportViewModel.onSaveClicked() },
|
||||||
open = { item -> openUri(item.uri, item.format.mimeType) },
|
open = { item -> openUri(item.uri, item.format.mimeType) },
|
||||||
),
|
),
|
||||||
@@ -324,6 +324,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
exportViewModel.onRequestSave(context)
|
exportViewModel.onRequestSave(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is ExportEvent.Share -> {
|
||||||
|
share(event.result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,10 +346,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun share(result: ExportResult?, viewModel: ExportViewModel) {
|
private fun share(result: ExportResult) {
|
||||||
if (result == null || result.files.isEmpty()) return
|
if (result.files.isEmpty()) return
|
||||||
|
|
||||||
viewModel.setAsShared()
|
|
||||||
|
|
||||||
val uris = result.files.map(::uriForFile)
|
val uris = result.files.map(::uriForFile)
|
||||||
val intent = Intent().apply {
|
val intent = Intent().apply {
|
||||||
|
|||||||
@@ -584,7 +584,7 @@ private fun ErrorBar(error: ExportError) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ExportError.toDisplayText(): Pair<String, String?> {
|
private fun ExportError.toDisplayText(): Pair<String, String?> {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is ExportError.OnPrepare -> {
|
is ExportError.OnPrepareOrShare -> {
|
||||||
val summary = message
|
val summary = message
|
||||||
val details = throwable.message
|
val details = throwable.message
|
||||||
summary to details
|
summary to details
|
||||||
@@ -684,7 +684,7 @@ fun PreviewExportScreenAfterSave() {
|
|||||||
fun ExportScreenPreviewWithError() {
|
fun ExportScreenPreviewWithError() {
|
||||||
ExportPreviewToCustomize(
|
ExportPreviewToCustomize(
|
||||||
ExportUiState(error =
|
ExportUiState(error =
|
||||||
ExportError.OnPrepare("PDF generation failed", IOException("Boom")))
|
ExportError.OnPrepareOrShare("PDF generation failed", IOException("Boom")))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ data class SaveDir(
|
|||||||
|
|
||||||
sealed class ExportError {
|
sealed class ExportError {
|
||||||
|
|
||||||
data class OnPrepare(
|
data class OnPrepareOrShare(
|
||||||
val message: String,
|
val message: String,
|
||||||
val throwable: Throwable,
|
val throwable: Throwable,
|
||||||
) : ExportError()
|
) : ExportError()
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import kotlin.coroutines.suspendCoroutine
|
|||||||
|
|
||||||
sealed interface ExportEvent {
|
sealed interface ExportEvent {
|
||||||
data object RequestSave : ExportEvent
|
data object RequestSave : ExportEvent
|
||||||
|
data class Share(val result: ExportResult) : ExportEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExportViewModel(container: AppContainer, val imageRepository: ImageRepository): ViewModel() {
|
class ExportViewModel(container: AppContainer, val imageRepository: ImageRepository): ViewModel() {
|
||||||
@@ -160,7 +161,7 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
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(error = ExportError.OnPrepare(message, e))
|
it.copy(error = ExportError.OnPrepareOrShare(message, e))
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
_uiState.update { it.copy(isGenerating = false) }
|
_uiState.update { it.copy(isGenerating = false) }
|
||||||
@@ -184,29 +185,27 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
ExportResult.Jpeg(files, sizeInBytes)
|
ExportResult.Jpeg(files, sizeInBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAsShared() {
|
private fun renameFile(source: File, target: File) {
|
||||||
_uiState.update { it.copy(hasShared = true) }
|
if (source.absolutePath == target.absolutePath) return
|
||||||
|
if (target.exists() && !target.delete()) {
|
||||||
|
throw IOException("Cannot delete existing file ${target.absolutePath}")
|
||||||
|
}
|
||||||
|
if (!source.renameTo(target)) {
|
||||||
|
throw IOException("Failed to rename ${source.name} to ${target.name}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyRenaming(): ExportResult? {
|
private fun applyRenaming(): ExportResult {
|
||||||
val result = _uiState.value.result ?: return null
|
val result = _uiState.value.result
|
||||||
|
?: throw IllegalStateException("Export result missing")
|
||||||
ensureValidFilename()
|
ensureValidFilename()
|
||||||
val filename = _uiState.value.filename
|
val filename = _uiState.value.filename
|
||||||
when (result) {
|
val updated = when (result) {
|
||||||
is ExportResult.Pdf -> {
|
is ExportResult.Pdf -> {
|
||||||
val fileName = FileManager.addPdfExtensionIfMissing(filename)
|
val fileName = FileManager.addPdfExtensionIfMissing(filename)
|
||||||
val newFile = File(result.file.parentFile, fileName)
|
val newFile = File(result.file.parentFile, fileName)
|
||||||
val tempFile = result.file
|
renameFile(result.file, newFile)
|
||||||
if (tempFile.absolutePath != newFile.absolutePath) {
|
ExportResult.Pdf(newFile, result.sizeInBytes, result.pageCount)
|
||||||
if (newFile.exists()) newFile.delete()
|
|
||||||
val success = tempFile.renameTo(newFile)
|
|
||||||
if (!success) return null
|
|
||||||
_uiState.update {
|
|
||||||
it.copy(result = ExportResult.Pdf(
|
|
||||||
newFile, result.sizeInBytes, result.pageCount)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is ExportResult.Jpeg -> {
|
is ExportResult.Jpeg -> {
|
||||||
val base = filename.removeSuffix(".jpg")
|
val base = filename.removeSuffix(".jpg")
|
||||||
@@ -214,17 +213,28 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
val renamedFiles = files.mapIndexed { index, file ->
|
val renamedFiles = files.mapIndexed { index, file ->
|
||||||
val indexSuffix = if (files.size == 1) "" else "_${index + 1}"
|
val indexSuffix = if (files.size == 1) "" else "_${index + 1}"
|
||||||
val newFile = File(file.parentFile, "${base}${indexSuffix}.jpg")
|
val newFile = File(file.parentFile, "${base}${indexSuffix}.jpg")
|
||||||
if (file.absolutePath != newFile.absolutePath) {
|
renameFile(file, newFile)
|
||||||
if (newFile.exists()) newFile.delete()
|
|
||||||
file.renameTo(newFile)
|
|
||||||
}
|
|
||||||
newFile
|
newFile
|
||||||
}
|
}
|
||||||
val updated = result.copy(jpegFiles = renamedFiles)
|
result.copy(jpegFiles = renamedFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
_uiState.update { it.copy(result = updated) }
|
_uiState.update { it.copy(result = updated) }
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onShareClicked() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val result = applyRenaming()
|
||||||
|
_events.emit(ExportEvent.Share(result))
|
||||||
|
_uiState.update { it.copy(hasShared = true) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val message = "Failed to prepare share"
|
||||||
|
logger.e("FairScan", message, e)
|
||||||
|
_uiState.update { it.copy(error = ExportError.OnPrepareOrShare(message, e)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _uiState.value.result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSaveClicked() {
|
fun onSaveClicked() {
|
||||||
|
|||||||
Reference in New Issue
Block a user