Beta 1.0 Release #11

Merged
niklas.ortmann merged 24 commits from dev into main 2026-10-03 00:37:20 +02:00
7 changed files with 342 additions and 3 deletions
Showing only changes of commit 08091a15a2 - Show all commits
@@ -29,10 +29,13 @@ import de.medapp.wunddoku.pdf.DocumentationPdfExporter
import de.medapp.wunddoku.pdf.DocumentationToPdfExportModelMapper import de.medapp.wunddoku.pdf.DocumentationToPdfExportModelMapper
import de.medapp.wunddoku.pdf.PdfExportError import de.medapp.wunddoku.pdf.PdfExportError
import de.medapp.wunddoku.pdf.PdfExportResult import de.medapp.wunddoku.pdf.PdfExportResult
import de.medapp.wunddoku.pdf.PdfToImagesConverter
import de.medapp.wunddoku.pdf.PdfToImagesResult
import de.medapp.wunddoku.signature.SignatureBitmapValidation import de.medapp.wunddoku.signature.SignatureBitmapValidation
import de.medapp.wunddoku.signature.SignatureStorage import de.medapp.wunddoku.signature.SignatureStorage
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import java.io.File
import java.time.Instant import java.time.Instant
import java.util.UUID import java.util.UUID
@@ -60,6 +63,7 @@ class DocumentationRepository(
private val signatureStorage: SignatureStorage, private val signatureStorage: SignatureStorage,
private val woundCarerRepository: WoundCarerRepository, private val woundCarerRepository: WoundCarerRepository,
private val pdfExporter: DocumentationPdfExporter, private val pdfExporter: DocumentationPdfExporter,
private val pdfToImagesConverter: PdfToImagesConverter,
private val db: AppDatabase, private val db: AppDatabase,
) { ) {
@@ -284,6 +288,16 @@ class DocumentationRepository(
return pdfExporter.export(model) return pdfExporter.export(model)
} }
/**
* Rendert eine bereits erzeugte PDF-Datei (s. [exportPdf]) seitenweise in JPEG-Dateien
* (Änderungswunsch: "PDF zusätzlich als JPEG speichern/senden können"). Nimmt bewusst die
* fertige [pdfFile] entgegen, statt die Dokumentation erneut zu laden/zu mappen - der
* Aufrufer hat sie direkt aus einem vorangegangenen [exportPdf]-Aufruf zur Verfügung, ein
* erneutes Laden wäre unnötige Doppelarbeit.
*/
suspend fun convertPdfToImages(documentationId: String, pdfFile: File): PdfToImagesResult =
pdfToImagesConverter.convert(documentationId, pdfFile)
/** /**
* Liefert eine speicherschonende Vorschauversion eines Fotos für die Anzeige im Fotogitter * Liefert eine speicherschonende Vorschauversion eines Fotos für die Anzeige im Fotogitter
* der Dokumentationsbearbeitung (kein Laden der vollen gespeicherten Auflösung). Wird von * der Dokumentationsbearbeitung (kein Laden der vollen gespeicherten Auflösung). Wird von
@@ -8,7 +8,9 @@ import de.medapp.wunddoku.data.repository.WoundCarerRepository
import de.medapp.wunddoku.media.InternalPhotoStorage import de.medapp.wunddoku.media.InternalPhotoStorage
import de.medapp.wunddoku.media.PhotoStorage import de.medapp.wunddoku.media.PhotoStorage
import de.medapp.wunddoku.pdf.AndroidPdfDocumentExporter import de.medapp.wunddoku.pdf.AndroidPdfDocumentExporter
import de.medapp.wunddoku.pdf.AndroidPdfToImagesConverter
import de.medapp.wunddoku.pdf.DocumentationPdfExporter import de.medapp.wunddoku.pdf.DocumentationPdfExporter
import de.medapp.wunddoku.pdf.PdfToImagesConverter
import de.medapp.wunddoku.signature.InternalSignatureStorage import de.medapp.wunddoku.signature.InternalSignatureStorage
import de.medapp.wunddoku.signature.SignatureStorage import de.medapp.wunddoku.signature.SignatureStorage
@@ -38,6 +40,7 @@ class AppContainer(context: Context) {
private val pdfExporter: DocumentationPdfExporter by lazy { private val pdfExporter: DocumentationPdfExporter by lazy {
AndroidPdfDocumentExporter(context, photoStorage, signatureStorage) AndroidPdfDocumentExporter(context, photoStorage, signatureStorage)
} }
private val pdfToImagesConverter: PdfToImagesConverter by lazy { AndroidPdfToImagesConverter(context) }
val patientRepository: PatientRepository by lazy { val patientRepository: PatientRepository by lazy {
PatientRepository(patientDao = database.patientDao()) PatientRepository(patientDao = database.patientDao())
@@ -62,6 +65,7 @@ class AppContainer(context: Context) {
signatureStorage = signatureStorage, signatureStorage = signatureStorage,
woundCarerRepository = woundCarerRepository, woundCarerRepository = woundCarerRepository,
pdfExporter = pdfExporter, pdfExporter = pdfExporter,
pdfToImagesConverter = pdfToImagesConverter,
db = database, db = database,
) )
} }
@@ -0,0 +1,99 @@
package de.medapp.wunddoku.pdf
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.Context
import android.content.Intent
import androidx.core.content.FileProvider
import java.io.File
/**
* Übergibt alle als JPEG gerenderten PDF-Seiten (s. [PdfToImagesConverter]) GEMEINSAM an den
* Android-"Senden an"-Dialog (`ACTION_SEND_MULTIPLE`) - Änderungswunsch: "alle generierten
* Bilder über 'Senden an' direkt verschicken können", nicht Seite für Seite einzeln.
*
* Bewusst getrennt von [PdfToImagesConverter] (reine Datei-Logik): das Starten einer Activity
* ist eine UI-/Plattform-Aktion, keine Repository-Aufgabe - exakt dieselbe Trennung wie bei
* [DocumentationPdfExporter] (Erzeugung) vs. [PdfOpener] (Anzeige).
*
* VulneraDoc implementiert ausdrücklich KEINEN eigenen Versand-Mechanismus; welche konkrete App
* (Mail, Messenger, Cloud-Speicher, ...) die Bilder entgegennimmt, entscheidet ausschließlich
* Android/der Nutzer über den System-Dialog.
*/
interface PdfImageSharer {
fun share(imageFiles: List<File>): PdfImageShareResult
}
sealed interface PdfImageShareResult {
data object Success : PdfImageShareResult
sealed interface Failure : PdfImageShareResult {
/** Es wurden keine Bilddateien übergeben - nichts zum Teilen vorhanden. */
data object NoImages : Failure
/** FileProvider konnte für mindestens eine Datei keine content://-Uri erzeugen. */
data class UriResolutionFailed(val message: String) : Failure
/** Keine installierte App kann Bilder (image/jpeg) entgegennehmen. */
data object NoAppAvailable : Failure
data class Unexpected(val message: String) : Failure
}
}
fun PdfImageShareResult.Failure.toUserMessage(): String = when (this) {
PdfImageShareResult.Failure.NoImages ->
"Es sind keine Bilder zum Versenden vorhanden."
is PdfImageShareResult.Failure.UriResolutionFailed ->
"Die Bilder konnten nicht zum Versenden freigegeben werden."
PdfImageShareResult.Failure.NoAppAvailable ->
"Auf diesem Gerät ist keine App zum Versenden von Bildern installiert."
is PdfImageShareResult.Failure.Unexpected ->
"Die Bilder konnten nicht versendet werden."
}
class AndroidPdfImageSharer(private val context: Context) : PdfImageSharer {
override fun share(imageFiles: List<File>): PdfImageShareResult {
if (imageFiles.isEmpty()) return PdfImageShareResult.Failure.NoImages
val authority = "${context.packageName}.fileprovider"
val uris = try {
imageFiles.map { file -> FileProvider.getUriForFile(context, authority, file) }
} catch (e: IllegalArgumentException) {
// Wird von FileProvider geworfen, wenn eine Datei außerhalb der in file_paths.xml
// freigegebenen Verzeichnisse liegt oder die Authority nicht passt.
return PdfImageShareResult.Failure.UriResolutionFailed(e.message ?: "Uri konnte nicht erzeugt werden.")
}
val intent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
type = "image/jpeg"
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Zusätzlich zum Flag oben wird die Leseberechtigung explizit über ClipData an JEDE
// einzelne Uri gehängt - bei ACTION_SEND_MULTIPLE mit mehreren content://-Uris ist
// das der zuverlässige Weg, damit wirklich ALLE empfangenden Apps Zugriff auf ALLE
// Bilder bekommen (nicht nur auf die im Intent-Datenfeld primär referenzierte).
clipData = ClipData.newUri(context.contentResolver, "Wunddokumentation", uris.first()).apply {
for (uri in uris.drop(1)) {
addItem(ClipData.Item(uri))
}
}
}
val chooser = Intent.createChooser(intent, "Bilder senden an")
return try {
context.startActivity(chooser)
PdfImageShareResult.Success
} catch (e: ActivityNotFoundException) {
// Bewusst KEIN vorheriges intent.resolveActivity(...) - s. Begründung in
// AndroidPdfOpener.open() (Package-Visibility-Einschränkungen ab Android 11).
PdfImageShareResult.Failure.NoAppAvailable
} catch (e: SecurityException) {
PdfImageShareResult.Failure.Unexpected(e.message ?: "Zugriff auf die Bilddateien wurde verweigert.")
} catch (e: Exception) {
PdfImageShareResult.Failure.Unexpected(e.message ?: "Unbekannter Fehler beim Versenden der Bilder.")
}
}
}
@@ -0,0 +1,117 @@
package de.medapp.wunddoku.pdf
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import kotlin.math.roundToInt
/**
* Rendert eine bereits fertig erzeugte PDF-Datei (s. [DocumentationPdfExporter]) seitenweise in
* einzelne JPEG-Dateien - Änderungswunsch: "PDF zusätzlich als JPEG speichern/senden können".
*
* Bewusst als eigener Schritt NACH dem PDF-Export statt als Alternative dazu: Es gibt weiterhin
* genau eine Quelle der Wahrheit für das Seitenlayout (der bestehende [PdfPageCursor]-basierte
* Export) - dieser Konverter rastert lediglich das bereits fertige Ergebnis, statt eine zweite,
* parallele Layout-Logik zu pflegen.
*
* Verwendet [android.graphics.pdf.PdfRenderer] (Android-Bordmittel seit API 21, unproblematisch
* bei minSdk 26) - keine zusätzliche Bibliothek nötig. Eine mehrseitige Wunddokumentation (Kopf/
* Blöcke, Signaturen, ggf. 30+ Fotoseiten) ergibt dabei bewusst EIN JPEG PRO SEITE, kein
* einzelnes Sammelbild - beim späteren Teilen (s. [PdfImageSharer]) werden alle Seiten
* gemeinsam als Bilder-Mehrfachauswahl übergeben.
*/
interface PdfToImagesConverter {
suspend fun convert(documentationId: String, pdfFile: File): PdfToImagesResult
}
sealed interface PdfToImagesResult {
data class Success(val imageFiles: List<File>) : PdfToImagesResult
sealed interface Failure : PdfToImagesResult {
/** Die übergebene PDF-Datei existiert nicht (mehr). */
data object FileNotFound : Failure
/** PdfRenderer meldet 0 Seiten - sollte bei einem selbst erzeugten PDF nie vorkommen. */
data object EmptyDocument : Failure
data class Unexpected(val message: String) : Failure
}
}
fun PdfToImagesResult.Failure.toUserMessage(): String = when (this) {
PdfToImagesResult.Failure.FileNotFound ->
"Die PDF-Datei wurde nicht gefunden. Bitte zuerst die Dokumentation erneut exportieren."
PdfToImagesResult.Failure.EmptyDocument ->
"Die PDF-Datei enthält keine Seiten."
is PdfToImagesResult.Failure.Unexpected ->
"Die PDF-Datei konnte nicht in Bilder umgewandelt werden."
}
class AndroidPdfToImagesConverter(private val context: Context) : PdfToImagesConverter {
override suspend fun convert(documentationId: String, pdfFile: File): PdfToImagesResult =
withContext(Dispatchers.IO) {
if (!pdfFile.exists()) return@withContext PdfToImagesResult.Failure.FileNotFound
val outputDir = File(File(context.filesDir, "pdf_images"), documentationId)
// Bilder eines vorherigen Exports zuerst entfernen, damit bei einer inzwischen
// kürzeren PDF (z. B. Fotos gelöscht) keine veralteten, zu vielen Seiten-Dateien
// liegen bleiben.
outputDir.deleteRecursively()
outputDir.mkdirs()
try {
ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY).use { fd ->
PdfRenderer(fd).use { renderer ->
if (renderer.pageCount == 0) {
return@withContext PdfToImagesResult.Failure.EmptyDocument
}
val imageFiles = mutableListOf<File>()
for (pageIndex in 0 until renderer.pageCount) {
renderer.openPage(pageIndex).use { page ->
val scale = RENDER_DPI / POINTS_PER_INCH
val width = (page.width * scale).roundToInt().coerceAtLeast(1)
val height = (page.height * scale).roundToInt().coerceAtLeast(1)
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
// WICHTIG: PdfRenderer zeichnet nur die tatsächlich im PDF
// vorhandenen Inhalte - unbedeckte Flächen bleiben transparent
// (Alpha 0). JPEG kennt keine Transparenz; ohne dieses
// Vorab-Weißfüllen würde jede unbedeckte Fläche beim
// Komprimieren als SCHWARZ statt als weißer Seitenhintergrund
// enden (bekannte PdfRenderer-Falle, s. offizielle Android-
// Beispiele).
bitmap.eraseColor(Color.WHITE)
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
val imageFile = File(outputDir, "seite_%02d.jpg".format(pageIndex + 1))
FileOutputStream(imageFile).use { out ->
bitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, out)
}
bitmap.recycle()
imageFiles.add(imageFile)
}
}
PdfToImagesResult.Success(imageFiles)
}
}
} catch (e: Exception) {
PdfToImagesResult.Failure.Unexpected(e.message ?: "Unbekannter Fehler bei der Bildkonvertierung.")
}
}
companion object {
/** PDF-Koordinaten sind in Punkten (1/72 Zoll) angegeben - diese Ziel-Auflösung ergibt
* ein für Ausdruck/Weitergabe ausreichend scharfes, aber nicht unnötig großes Bild. */
private const val RENDER_DPI = 200f
private const val POINTS_PER_INCH = 72f
private const val JPEG_QUALITY = 90
}
}
@@ -30,6 +30,7 @@ import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.PictureAsPdf import androidx.compose.material.icons.filled.PictureAsPdf
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
@@ -75,7 +76,9 @@ import de.medapp.wunddoku.domain.model.CheckItem
import de.medapp.wunddoku.domain.model.DocumentationBlock import de.medapp.wunddoku.domain.model.DocumentationBlock
import de.medapp.wunddoku.domain.model.Photo import de.medapp.wunddoku.domain.model.Photo
import de.medapp.wunddoku.domain.model.Signature import de.medapp.wunddoku.domain.model.Signature
import de.medapp.wunddoku.pdf.AndroidPdfImageSharer
import de.medapp.wunddoku.pdf.AndroidPdfOpener import de.medapp.wunddoku.pdf.AndroidPdfOpener
import de.medapp.wunddoku.pdf.PdfImageShareResult
import de.medapp.wunddoku.pdf.PdfOpenResult import de.medapp.wunddoku.pdf.PdfOpenResult
import de.medapp.wunddoku.pdf.toUserMessage import de.medapp.wunddoku.pdf.toUserMessage
import de.medapp.wunddoku.ui.documentation.edit.signature.AddSignatureDialog import de.medapp.wunddoku.ui.documentation.edit.signature.AddSignatureDialog
@@ -137,6 +140,7 @@ fun DocumentationEditScreen(
// Starten einer externen Activity ist eine UI-/Plattform-Aktion, keine Repository-Aufgabe. // Starten einer externen Activity ist eine UI-/Plattform-Aktion, keine Repository-Aufgabe.
val context = LocalContext.current val context = LocalContext.current
val pdfOpener = remember(context) { AndroidPdfOpener(context) } val pdfOpener = remember(context) { AndroidPdfOpener(context) }
val pdfImageSharer = remember(context) { AndroidPdfImageSharer(context) }
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(uiState.pdfExportMessage) { LaunchedEffect(uiState.pdfExportMessage) {
val message = uiState.pdfExportMessage ?: return@LaunchedEffect val message = uiState.pdfExportMessage ?: return@LaunchedEffect
@@ -157,6 +161,24 @@ fun DocumentationEditScreen(
viewModel.dismissPdfExportMessage() viewModel.dismissPdfExportMessage()
} }
// Ä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-
// Share-Dialog - Intent/Activity-Start ist bewusst UI-Aufgabe, nicht Sache des ViewModels.
LaunchedEffect(uiState.imagesToShare) {
val images = uiState.imagesToShare ?: return@LaunchedEffect
when (val shareResult = pdfImageSharer.share(images)) {
PdfImageShareResult.Success -> Unit
is PdfImageShareResult.Failure -> snackbarHostState.showSnackbar(shareResult.toUserMessage())
}
viewModel.onImageShareHandled()
}
LaunchedEffect(uiState.imageShareMessage) {
val message = uiState.imageShareMessage ?: return@LaunchedEffect
snackbarHostState.showSnackbar(message)
viewModel.dismissImageShareMessage()
}
Scaffold( Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = { topBar = {
@@ -175,9 +197,8 @@ fun DocumentationEditScreen(
} }
}, },
actions = { actions = {
// PDF-005 (Öffnen)/PDF-006 (Teilen) folgen später - dieser Button erzeugt // PDF-005 (Öffnen) ist umgesetzt (s. LaunchedEffect oben); dieser Button
// ausschließlich die Datei im internen Speicher (PDF-001/002), als // erzeugt die Datei im internen Speicher (PDF-001/002).
// minimaler Trigger zum manuellen Testen der Export-Funktionalität.
if (uiState.pdfExportInProgress) { if (uiState.pdfExportInProgress) {
CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp) CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp)
} else { } else {
@@ -185,6 +206,16 @@ fun DocumentationEditScreen(
Icon(Icons.Filled.PictureAsPdf, contentDescription = "PDF erzeugen") 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) {
CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp)
} else {
IconButton(onClick = viewModel::exportAndShareAsImages) {
Icon(Icons.Filled.Share, contentDescription = "Als Bilder senden")
}
}
// "Fertig" beendet die Bearbeitung bereits jetzt (Inhalte sind ohnehin per // "Fertig" beendet die Bearbeitung bereits jetzt (Inhalte sind ohnehin per
// Autosave gesichert, kein expliziter Speichern-Button nötig). // Autosave gesichert, kein expliziter Speichern-Button nötig).
Button(onClick = onFinished) { Text("Fertig") } Button(onClick = onFinished) { Text("Fertig") }
@@ -15,6 +15,8 @@ import de.medapp.wunddoku.domain.model.Documentation
import de.medapp.wunddoku.domain.model.WoundCarer import de.medapp.wunddoku.domain.model.WoundCarer
import de.medapp.wunddoku.pdf.PdfExportError import de.medapp.wunddoku.pdf.PdfExportError
import de.medapp.wunddoku.pdf.PdfExportResult import de.medapp.wunddoku.pdf.PdfExportResult
import de.medapp.wunddoku.pdf.PdfToImagesResult
import de.medapp.wunddoku.pdf.toUserMessage
import de.medapp.wunddoku.ui.navigation.Screen import de.medapp.wunddoku.ui.navigation.Screen
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@@ -63,6 +65,20 @@ data class DocumentationEditUiState(
* referenziert werden kann, während ein neuer Export läuft. * referenziert werden kann, während ein neuer Export läuft.
*/ */
val lastExportedPdfFile: File? = null, 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,
/**
* Frisch erzeugte JPEG-Seiten, bereit zum Teilen (Änderungswunsch: "alle generierten Bilder
* über 'Senden an' direkt verschicken können"). Wird vom Screen per LaunchedEffect
* beobachtet, der daraufhin den System-"Senden an"-Dialog öffnet und anschließend
* [DocumentationEditViewModel.onImageShareHandled] aufruft - das Teilen selbst (Intent/
* Activity-Start) ist bewusst UI-Aufgabe, nicht Sache des ViewModels.
*/
val imagesToShare: List<File>? = null,
) { ) {
val blockCount: Int get() = documentation?.blocks?.size ?: 0 val blockCount: Int get() = documentation?.blocks?.size ?: 0
@@ -357,6 +373,63 @@ class DocumentationEditViewModel(
_uiState.update { it.copy(pdfExportMessage = null) } _uiState.update { it.copy(pdfExportMessage = null) }
} }
/**
* 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
* können"). Bewusst ein eigener, vollständiger Export-Lauf statt Wiederverwendung von
* [uiState.lastExportedPdfFile]: so spiegeln die Bilder IMMER den aktuellen Stand wider,
* unabhängig davon, ob/wann zuvor schon einmal "PDF erzeugen" angetippt wurde.
*
* Das eigentliche Teilen (Android-"Senden an"-Dialog) übernimmt NICHT diese Methode, sondern
* der Screen anhand von [DocumentationEditUiState.imagesToShare] - Intent/Activity-Start ist
* UI-Aufgabe (s. [de.medapp.wunddoku.pdf.PdfImageSharer]).
*/
fun exportAndShareAsImages() {
viewModelScope.launch {
_uiState.update {
it.copy(imageShareInProgress = true, imageShareMessage = null, imagesToShare = null)
}
val exportResult = repository.exportPdf(documentationId)
val pdfFile = when (exportResult) {
is PdfExportResult.Success -> exportResult.file
is PdfExportResult.Failure -> {
_uiState.update {
it.copy(
imageShareInProgress = false,
imageShareMessage = "PDF-Erzeugung fehlgeschlagen: ${exportResult.error.toUserMessage()}",
)
}
return@launch
}
}
when (val imagesResult = repository.convertPdfToImages(documentationId, pdfFile)) {
is PdfToImagesResult.Success -> _uiState.update {
it.copy(imageShareInProgress = false, imagesToShare = imagesResult.imageFiles)
}
is PdfToImagesResult.Failure -> _uiState.update {
it.copy(
imageShareInProgress = false,
imageShareMessage = "Bildexport fehlgeschlagen: ${imagesResult.toUserMessage()}",
)
}
}
}
}
/** Wird vom Screen aufgerufen, nachdem der "Senden an"-Dialog für [DocumentationEditUiState.imagesToShare]
* geöffnet wurde (erfolgreich oder nicht) - verhindert ein erneutes Öffnen bei der nächsten
* Recomposition, solange keine neuen Bilder erzeugt wurden. */
fun onImageShareHandled() {
_uiState.update { it.copy(imagesToShare = null) }
}
fun dismissImageShareMessage() {
_uiState.update { it.copy(imageShareMessage = null) }
}
private fun PdfExportError.toUserMessage(): String = when (this) { private fun PdfExportError.toUserMessage(): String = when (this) {
PdfExportError.DocumentationNotFound -> "Die Dokumentation wurde nicht gefunden." PdfExportError.DocumentationNotFound -> "Die Dokumentation wurde nicht gefunden."
is PdfExportError.DirectoryCreationFailed -> "Zielverzeichnis konnte nicht angelegt werden." is PdfExportError.DirectoryCreationFailed -> "Zielverzeichnis konnte nicht angelegt werden."
+1
View File
@@ -7,6 +7,7 @@
--> -->
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="pdf_exports" path="pdf/" /> <files-path name="pdf_exports" path="pdf/" />
<files-path name="pdf_images" path="pdf_images/" />
<files-path name="photos" path="photos/" /> <files-path name="photos" path="photos/" />
<files-path name="signatures" path="signatures/" /> <files-path name="signatures" path="signatures/" />
</paths> </paths>