diff --git a/app/src/main/java/no/naiv/meddetsamme/pdf/DoctorSummaryPdf.kt b/app/src/main/java/no/naiv/meddetsamme/pdf/DoctorSummaryPdf.kt index eeac265..a1c74b4 100644 --- a/app/src/main/java/no/naiv/meddetsamme/pdf/DoctorSummaryPdf.kt +++ b/app/src/main/java/no/naiv/meddetsamme/pdf/DoctorSummaryPdf.kt @@ -46,8 +46,10 @@ class DoctorSummaryPdf(private val context: Context) { ) /** Generates the PDF and returns a ready-to-launch share chooser intent. */ - suspend fun buildShareIntent(): Intent { - val file = render() + suspend fun buildShareIntent(): Intent = shareIntentFor(render()) + + /** Share chooser for an already-rendered summary (the preview screen's path). */ + fun shareIntentFor(file: File): Intent { val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) val send = Intent(Intent.ACTION_SEND) .setType("application/pdf") diff --git a/app/src/main/java/no/naiv/meddetsamme/ui/Nav.kt b/app/src/main/java/no/naiv/meddetsamme/ui/Nav.kt index 5e6b4b1..72f9507 100644 --- a/app/src/main/java/no/naiv/meddetsamme/ui/Nav.kt +++ b/app/src/main/java/no/naiv/meddetsamme/ui/Nav.kt @@ -21,6 +21,7 @@ sealed interface Screen { /** itemId == null → create new. */ data class ItemEdit(val itemId: Long?) : Screen data object Settings : Screen + data object SummaryPreview : Screen } class Nav(initial: Screen) { @@ -50,5 +51,6 @@ fun AppRoot() { is Screen.Inventory -> InventoryScreen(nav) is Screen.ItemEdit -> ItemEditScreen(nav, screen.itemId) is Screen.Settings -> SettingsScreen(nav) + is Screen.SummaryPreview -> SummaryPreviewScreen(nav) } } diff --git a/app/src/main/java/no/naiv/meddetsamme/ui/SettingsScreen.kt b/app/src/main/java/no/naiv/meddetsamme/ui/SettingsScreen.kt index 6898673..6e7d10b 100644 --- a/app/src/main/java/no/naiv/meddetsamme/ui/SettingsScreen.kt +++ b/app/src/main/java/no/naiv/meddetsamme/ui/SettingsScreen.kt @@ -45,7 +45,6 @@ import no.naiv.meddetsamme.R import no.naiv.meddetsamme.alarm.AlarmScheduler import no.naiv.meddetsamme.backup.BackupManager import no.naiv.meddetsamme.fest.FestRepository -import no.naiv.meddetsamme.pdf.DoctorSummaryPdf import no.naiv.meddetsamme.settings.SettingsStore @OptIn(ExperimentalMaterial3Api::class) @@ -204,17 +203,9 @@ fun SettingsScreen(nav: Nav) { HorizontalDivider() Text("Legeoversikt", style = MaterialTheme.typography.titleMedium) OutlinedButton( - onClick = { - scope.launch { - try { - context.startActivity(DoctorSummaryPdf(context).buildShareIntent()) - } catch (e: Exception) { - toast("Kunne ikke lage PDF: ${e.message}") - } - } - }, + onClick = { nav.push(Screen.SummaryPreview) }, modifier = Modifier.fillMaxWidth(), - ) { Text("Del medisinoversikt (PDF)") } + ) { Text("Vis medisinoversikt (PDF)") } } } diff --git a/app/src/main/java/no/naiv/meddetsamme/ui/SummaryPreviewScreen.kt b/app/src/main/java/no/naiv/meddetsamme/ui/SummaryPreviewScreen.kt new file mode 100644 index 0000000..f1fe7fb --- /dev/null +++ b/app/src/main/java/no/naiv/meddetsamme/ui/SummaryPreviewScreen.kt @@ -0,0 +1,136 @@ +package no.naiv.meddetsamme.ui + +import android.graphics.Bitmap +import android.graphics.pdf.PdfRenderer +import android.os.ParcelFileDescriptor +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import no.naiv.meddetsamme.R +import no.naiv.meddetsamme.pdf.DoctorSummaryPdf + +/** + * In-app preview of the doctor summary: the generated PDF rendered page by + * page with the platform PdfRenderer (no viewer app needed, no dependencies), + * with share in the top bar. See before you send. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SummaryPreviewScreen(nav: Nav) { + val context = LocalContext.current + var pages by remember { mutableStateOf>(emptyList()) } + var pdfFile by remember { mutableStateOf(null) } + var error by remember { mutableStateOf(null) } + + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + try { + val pdf = DoctorSummaryPdf(context) + val file = pdf.render() + val rendered = mutableListOf() + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).use { fd -> + PdfRenderer(fd).use { renderer -> + for (i in 0 until renderer.pageCount) { + renderer.openPage(i).use { page -> + // 2x point size — crisp on phone screens without + // burning memory on huge bitmaps. + val bmp = Bitmap.createBitmap(page.width * 2, page.height * 2, Bitmap.Config.ARGB_8888) + bmp.eraseColor(android.graphics.Color.WHITE) + page.render(bmp, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) + rendered += bmp + } + } + } + } + pdfFile = file + pages = rendered + } catch (e: Exception) { + error = e.message ?: "Ukjent feil" + } + } + } + + Scaffold( + topBar = { + TopAppBar( + title = { Text("Medisinoversikt") }, + navigationIcon = { + IconButton(onClick = { nav.pop() }) { + Icon(painterResource(R.drawable.ic_back), contentDescription = "Tilbake") + } + }, + actions = { + IconButton( + onClick = { + pdfFile?.let { + context.startActivity(DoctorSummaryPdf(context).shareIntentFor(it)) + } + }, + enabled = pdfFile != null, + ) { + Icon(painterResource(R.drawable.ic_share), contentDescription = "Del PDF") + } + }, + ) + }, + ) { padding -> + when { + error != null -> Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) { + Text("Kunne ikke lage oversikten: $error", color = MaterialTheme.colorScheme.error) + } + pages.isEmpty() -> Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + else -> LazyColumn( + modifier = Modifier.fillMaxSize().padding(padding), + contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + itemsIndexed(pages) { index, bmp -> + Surface( + shadowElevation = 2.dp, + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth(), + ) { + Image( + bitmap = bmp.asImageBitmap(), + contentDescription = "Side ${index + 1} av ${pages.size} av medisinoversikten", + contentScale = ContentScale.FillWidth, + modifier = Modifier.fillMaxWidth(), + ) + } + } + } + } + } +} diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 0000000..5adabbd --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,8 @@ + + + +