MEDIA-005
Export fertig
This commit is contained in:
+72
-10
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
@@ -46,6 +47,7 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -66,6 +68,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
@@ -78,8 +81,10 @@ import de.medapp.wunddoku.domain.model.Photo
|
||||
import de.medapp.wunddoku.domain.model.Signature
|
||||
import de.medapp.wunddoku.pdf.AndroidPdfImageSharer
|
||||
import de.medapp.wunddoku.pdf.AndroidPdfOpener
|
||||
import de.medapp.wunddoku.pdf.AndroidPdfSharer
|
||||
import de.medapp.wunddoku.pdf.PdfImageShareResult
|
||||
import de.medapp.wunddoku.pdf.PdfOpenResult
|
||||
import de.medapp.wunddoku.pdf.PdfShareResult
|
||||
import de.medapp.wunddoku.pdf.toUserMessage
|
||||
import de.medapp.wunddoku.ui.documentation.edit.signature.AddSignatureDialog
|
||||
|
||||
@@ -141,6 +146,7 @@ fun DocumentationEditScreen(
|
||||
val context = LocalContext.current
|
||||
val pdfOpener = remember(context) { AndroidPdfOpener(context) }
|
||||
val pdfImageSharer = remember(context) { AndroidPdfImageSharer(context) }
|
||||
val pdfSharer = remember(context) { AndroidPdfSharer(context) }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
LaunchedEffect(uiState.pdfExportMessage) {
|
||||
val message = uiState.pdfExportMessage ?: return@LaunchedEffect
|
||||
@@ -163,7 +169,7 @@ fun DocumentationEditScreen(
|
||||
|
||||
// Änderungswunsch: erzeugte Bilder direkt über den Android-"Senden an"-Dialog verschicken
|
||||
// können. Sobald das ViewModel frische JPEG-Seiten bereitstellt (s.
|
||||
// DocumentationEditViewModel.exportAndShareAsImages), öffnet dieser Effekt den System-
|
||||
// DocumentationEditViewModel.shareAsImages), öffnet dieser Effekt den System-
|
||||
// Share-Dialog - Intent/Activity-Start ist bewusst UI-Aufgabe, nicht Sache des ViewModels.
|
||||
LaunchedEffect(uiState.imagesToShare) {
|
||||
val images = uiState.imagesToShare ?: return@LaunchedEffect
|
||||
@@ -173,10 +179,20 @@ fun DocumentationEditScreen(
|
||||
}
|
||||
viewModel.onImageShareHandled()
|
||||
}
|
||||
LaunchedEffect(uiState.imageShareMessage) {
|
||||
val message = uiState.imageShareMessage ?: return@LaunchedEffect
|
||||
// Analog zum Bilderversand oben, nur für die Format-Auswahl "PDF" (s.
|
||||
// DocumentationEditViewModel.shareAsPdf).
|
||||
LaunchedEffect(uiState.pdfToShare) {
|
||||
val pdfFile = uiState.pdfToShare ?: return@LaunchedEffect
|
||||
when (val shareResult = pdfSharer.share(pdfFile)) {
|
||||
PdfShareResult.Success -> Unit
|
||||
is PdfShareResult.Failure -> snackbarHostState.showSnackbar(shareResult.toUserMessage())
|
||||
}
|
||||
viewModel.onPdfShareHandled()
|
||||
}
|
||||
LaunchedEffect(uiState.shareMessage) {
|
||||
val message = uiState.shareMessage ?: return@LaunchedEffect
|
||||
snackbarHostState.showSnackbar(message)
|
||||
viewModel.dismissImageShareMessage()
|
||||
viewModel.dismissShareMessage()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -206,14 +222,14 @@ fun DocumentationEditScreen(
|
||||
Icon(Icons.Filled.PictureAsPdf, contentDescription = "PDF erzeugen")
|
||||
}
|
||||
}
|
||||
// Änderungswunsch: PDF zusätzlich als JPEG speichern/versenden. Exportiert
|
||||
// intern frisch als PDF, wandelt seitenweise in JPEG um und öffnet
|
||||
// anschließend automatisch den "Senden an"-Dialog (s. LaunchedEffect oben).
|
||||
if (uiState.imageShareInProgress) {
|
||||
// Änderungswunsch: PDF zusätzlich als JPEG speichern/versenden, wahlweise
|
||||
// auch als PDF selbst - Format wird per Dialog abgefragt (s. weiter unten),
|
||||
// BEVOR irgendetwas exportiert wird.
|
||||
if (uiState.shareInProgress) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp)
|
||||
} else {
|
||||
IconButton(onClick = viewModel::exportAndShareAsImages) {
|
||||
Icon(Icons.Filled.Share, contentDescription = "Als Bilder senden")
|
||||
IconButton(onClick = viewModel::requestShareFormatChoice) {
|
||||
Icon(Icons.Filled.Share, contentDescription = "Teilen")
|
||||
}
|
||||
}
|
||||
// "Fertig" beendet die Bearbeitung bereits jetzt (Inhalte sind ohnehin per
|
||||
@@ -346,6 +362,52 @@ fun DocumentationEditScreen(
|
||||
onDismiss = { viewedPhoto = null },
|
||||
)
|
||||
}
|
||||
|
||||
// Änderungswunsch: Format-Auswahl beim Antippen des Teilen-Buttons.
|
||||
if (uiState.showShareFormatDialog) {
|
||||
ShareFormatDialog(
|
||||
onDismiss = viewModel::dismissShareFormatDialog,
|
||||
onChooseImages = viewModel::shareAsImages,
|
||||
onChoosePdf = viewModel::shareAsPdf,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fragt beim Antippen des Teilen-Buttons ab, ob als JPG (Bilder, eine Datei pro Seite) oder als
|
||||
* PDF (die Datei selbst) geteilt werden soll (Änderungswunsch). Erst NACH dieser Auswahl wird
|
||||
* überhaupt exportiert (s. [DocumentationEditViewModel.shareAsImages]/[DocumentationEditViewModel.shareAsPdf]) -
|
||||
* kein unnötiger Export, falls der Nutzer den Dialog stattdessen abbricht.
|
||||
*/
|
||||
@Composable
|
||||
private fun ShareFormatDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onChooseImages: () -> Unit,
|
||||
onChoosePdf: () -> Unit,
|
||||
) {
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Surface(shape = RoundedCornerShape(16.dp), color = MaterialTheme.colorScheme.surface) {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(20.dp)) {
|
||||
Text("Teilen als", style = MaterialTheme.typography.titleMedium)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
"Als Bilder (JPG) oder als PDF teilen?",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.action_cancel)) }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
OutlinedButton(onClick = onChooseImages) { Text("JPG") }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Button(onClick = onChoosePdf) { Text("PDF") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+80
-15
@@ -65,12 +65,17 @@ data class DocumentationEditUiState(
|
||||
* referenziert werden kann, während ein neuer Export läuft.
|
||||
*/
|
||||
val lastExportedPdfFile: File? = null,
|
||||
/** true, während PDF-Export + JPEG-Konvertierung für den Bilderversand laufen (ein
|
||||
* gemeinsamer Ladezustand für beide Schritte, s. [DocumentationEditViewModel.exportAndShareAsImages]). */
|
||||
val imageShareInProgress: Boolean = false,
|
||||
/** Fehlermeldung, falls Export oder Konvertierung fehlgeschlagen sind - erfolgreich
|
||||
* erzeugte Bilder lösen dagegen KEINE Meldung aus, sondern direkt [imagesToShare]. */
|
||||
val imageShareMessage: String? = null,
|
||||
/** true, während PDF-Export + Konvertierung für den Versand laufen - gemeinsamer
|
||||
* Ladezustand für BEIDE Formate (JPG über [DocumentationEditViewModel.shareAsImages],
|
||||
* PDF über [DocumentationEditViewModel.shareAsPdf]), da immer zuerst frisch exportiert
|
||||
* wird. */
|
||||
val shareInProgress: Boolean = false,
|
||||
/** Fehlermeldung, falls Export oder Konvertierung fehlgeschlagen sind - ein erfolgreicher
|
||||
* Export löst dagegen KEINE Meldung aus, sondern direkt [imagesToShare] bzw. [pdfToShare]. */
|
||||
val shareMessage: String? = null,
|
||||
/** true, solange die Auswahl "Als JPG oder PDF teilen?" angezeigt wird (Änderungswunsch:
|
||||
* Format-Auswahl beim Antippen des Teilen-Buttons, bevor irgendetwas exportiert wird). */
|
||||
val showShareFormatDialog: Boolean = false,
|
||||
/**
|
||||
* Frisch erzeugte JPEG-Seiten, bereit zum Teilen (Änderungswunsch: "alle generierten Bilder
|
||||
* über 'Senden an' direkt verschicken können"). Wird vom Screen per LaunchedEffect
|
||||
@@ -79,6 +84,9 @@ data class DocumentationEditUiState(
|
||||
* Activity-Start) ist bewusst UI-Aufgabe, nicht Sache des ViewModels.
|
||||
*/
|
||||
val imagesToShare: List<File>? = null,
|
||||
/** Analog [imagesToShare], nur für die Variante "als PDF teilen" - Gegenstück
|
||||
* [DocumentationEditViewModel.onPdfShareHandled]. */
|
||||
val pdfToShare: File? = null,
|
||||
) {
|
||||
val blockCount: Int get() = documentation?.blocks?.size ?: 0
|
||||
|
||||
@@ -373,6 +381,31 @@ class DocumentationEditViewModel(
|
||||
_uiState.update { it.copy(pdfExportMessage = null) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet die Auswahl "Als JPG oder PDF teilen?" (Änderungswunsch: Format-Auswahl beim
|
||||
* Antippen des Teilen-Buttons, BEVOR irgendetwas exportiert wird - vorher wurde beim
|
||||
* Antippen sofort und ausschließlich als JPG geteilt).
|
||||
*/
|
||||
fun requestShareFormatChoice() {
|
||||
_uiState.update { it.copy(showShareFormatDialog = true) }
|
||||
}
|
||||
|
||||
fun dismissShareFormatDialog() {
|
||||
_uiState.update { it.copy(showShareFormatDialog = false) }
|
||||
}
|
||||
|
||||
/** Nutzer hat sich für "Als JPG teilen" entschieden. */
|
||||
fun shareAsImages() {
|
||||
_uiState.update { it.copy(showShareFormatDialog = false) }
|
||||
exportAndShareAsImages()
|
||||
}
|
||||
|
||||
/** Nutzer hat sich für "Als PDF teilen" entschieden. */
|
||||
fun shareAsPdf() {
|
||||
_uiState.update { it.copy(showShareFormatDialog = false) }
|
||||
exportAndSharePdf()
|
||||
}
|
||||
|
||||
/**
|
||||
* Exportiert die Dokumentation frisch als PDF und wandelt das Ergebnis anschließend
|
||||
* seitenweise in JPEG-Bilder um (Änderungswunsch: "PDF zusätzlich als JPEG speichern/senden
|
||||
@@ -384,10 +417,10 @@ class DocumentationEditViewModel(
|
||||
* der Screen anhand von [DocumentationEditUiState.imagesToShare] - Intent/Activity-Start ist
|
||||
* UI-Aufgabe (s. [de.medapp.wunddoku.pdf.PdfImageSharer]).
|
||||
*/
|
||||
fun exportAndShareAsImages() {
|
||||
private fun exportAndShareAsImages() {
|
||||
viewModelScope.launch {
|
||||
_uiState.update {
|
||||
it.copy(imageShareInProgress = true, imageShareMessage = null, imagesToShare = null)
|
||||
it.copy(shareInProgress = true, shareMessage = null, imagesToShare = null)
|
||||
}
|
||||
|
||||
val exportResult = repository.exportPdf(documentationId)
|
||||
@@ -396,8 +429,8 @@ class DocumentationEditViewModel(
|
||||
is PdfExportResult.Failure -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
imageShareInProgress = false,
|
||||
imageShareMessage = "PDF-Erzeugung fehlgeschlagen: ${exportResult.error.toUserMessage()}",
|
||||
shareInProgress = false,
|
||||
shareMessage = "PDF-Erzeugung fehlgeschlagen: ${exportResult.error.toUserMessage()}",
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
@@ -406,13 +439,40 @@ class DocumentationEditViewModel(
|
||||
|
||||
when (val imagesResult = repository.convertPdfToImages(documentationId, pdfFile)) {
|
||||
is PdfToImagesResult.Success -> _uiState.update {
|
||||
it.copy(imageShareInProgress = false, imagesToShare = imagesResult.imageFiles)
|
||||
it.copy(shareInProgress = false, imagesToShare = imagesResult.imageFiles)
|
||||
}
|
||||
|
||||
is PdfToImagesResult.Failure -> _uiState.update {
|
||||
it.copy(
|
||||
imageShareInProgress = false,
|
||||
imageShareMessage = "Bildexport fehlgeschlagen: ${imagesResult.toUserMessage()}",
|
||||
shareInProgress = false,
|
||||
shareMessage = "Bildexport fehlgeschlagen: ${imagesResult.toUserMessage()}",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exportiert die Dokumentation frisch als PDF und stellt sie zum Teilen bereit
|
||||
* (Änderungswunsch: Format-Auswahl "PDF" statt "JPG") - ohne die JPEG-Konvertierung, die
|
||||
* geteilte Datei ist die PDF selbst. Das eigentliche Teilen übernimmt der Screen anhand von
|
||||
* [DocumentationEditUiState.pdfToShare] (s. [de.medapp.wunddoku.pdf.PdfSharer]).
|
||||
*/
|
||||
private fun exportAndSharePdf() {
|
||||
viewModelScope.launch {
|
||||
_uiState.update {
|
||||
it.copy(shareInProgress = true, shareMessage = null, pdfToShare = null)
|
||||
}
|
||||
|
||||
when (val exportResult = repository.exportPdf(documentationId)) {
|
||||
is PdfExportResult.Success -> _uiState.update {
|
||||
it.copy(shareInProgress = false, pdfToShare = exportResult.file)
|
||||
}
|
||||
|
||||
is PdfExportResult.Failure -> _uiState.update {
|
||||
it.copy(
|
||||
shareInProgress = false,
|
||||
shareMessage = "PDF-Erzeugung fehlgeschlagen: ${exportResult.error.toUserMessage()}",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -426,8 +486,13 @@ class DocumentationEditViewModel(
|
||||
_uiState.update { it.copy(imagesToShare = null) }
|
||||
}
|
||||
|
||||
fun dismissImageShareMessage() {
|
||||
_uiState.update { it.copy(imageShareMessage = null) }
|
||||
/** Analog [onImageShareHandled], nur für [DocumentationEditUiState.pdfToShare]. */
|
||||
fun onPdfShareHandled() {
|
||||
_uiState.update { it.copy(pdfToShare = null) }
|
||||
}
|
||||
|
||||
fun dismissShareMessage() {
|
||||
_uiState.update { it.copy(shareMessage = null) }
|
||||
}
|
||||
|
||||
private fun PdfExportError.toUserMessage(): String = when (this) {
|
||||
|
||||
Reference in New Issue
Block a user