In-app preview of the doctor summary before sharing
New SummaryPreviewScreen renders the generated PDF page by page with the platform PdfRenderer (2x scale, no new dependencies) and puts share in the top bar — see before you send. The settings button now opens the preview instead of jumping straight to the share sheet; the share path reuses the already-rendered file via DoctorSummaryPdf.shareIntentFor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b7152f56ef
commit
e16fd72d06
5 changed files with 152 additions and 13 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)") }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
136
app/src/main/java/no/naiv/meddetsamme/ui/SummaryPreviewScreen.kt
Normal file
136
app/src/main/java/no/naiv/meddetsamme/ui/SummaryPreviewScreen.kt
Normal file
|
|
@ -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<List<Bitmap>>(emptyList()) }
|
||||
var pdfFile by remember { mutableStateOf<File?>(null) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val pdf = DoctorSummaryPdf(context)
|
||||
val file = pdf.render()
|
||||
val rendered = mutableListOf<Bitmap>()
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
app/src/main/res/drawable/ic_share.xml
Normal file
8
app/src/main/res/drawable/ic_share.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp" android:height="24dp"
|
||||
android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92 -1.31,-2.92 -2.92,-2.92z" />
|
||||
</vector>
|
||||
Loading…
Add table
Add a link
Reference in a new issue