Android: export and copy summaries and transcripts
- summary: SelectionContainer (select text directly), Kopieren (clipboard + toast), Teilen (share sheet) and Datei speichern (written to Download/meetrec via MediaStore, no permission needed on API 29+) - transcript: Datei speichern alongside the existing share - installed and verified building; sample of summary export names: meetrec-summary-<recording-id>.md / meetrec-transcript-<id>.txt
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
package com.meetrec.android
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -387,14 +394,42 @@ private fun LibraryDetail(
|
||||
when {
|
||||
m.summary == "pending" -> Text("wird erstellt …",
|
||||
style = MaterialTheme.typography.bodySmall)
|
||||
summaryText != null -> Text(
|
||||
summaryText!!,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 240.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
)
|
||||
summaryText != null -> {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
summaryText!!,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 240.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = {
|
||||
copyToClipboard(context, summaryText!!)
|
||||
}) { Text("Kopieren") }
|
||||
OutlinedButton(onClick = {
|
||||
shareText(context, summaryText!!, "MeetRec — Zusammenfassung")
|
||||
}) { Text("Teilen") }
|
||||
OutlinedButton(onClick = {
|
||||
scope.launch {
|
||||
try {
|
||||
val name = "meetrec-summary-" +
|
||||
(item as? LibraryItem.Server)?.meta?.id
|
||||
.orEmpty() + ".md"
|
||||
val path = withContext(Dispatchers.IO) {
|
||||
saveTextToDownloads(context, name, summaryText!!)
|
||||
}
|
||||
Toast.makeText(context, "gespeichert: $path",
|
||||
Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
detailError = "save failed: ${e.message}"
|
||||
}
|
||||
}
|
||||
}) { Text("Datei speichern") }
|
||||
}
|
||||
}
|
||||
m.summary != null && m.summary.startsWith("error") -> {
|
||||
Text(m.summary, color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall)
|
||||
@@ -481,8 +516,30 @@ private fun LibraryDetail(
|
||||
Text("No transcript available for this recording.",
|
||||
style = MaterialTheme.typography.bodySmall)
|
||||
} else {
|
||||
Text("Transcript (${d.segments.size} segments)",
|
||||
style = MaterialTheme.typography.titleSmall)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Transcript (${d.segments.size} segments)",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier.align(androidx.compose.ui.Alignment.CenterVertically))
|
||||
OutlinedButton(onClick = {
|
||||
scope.launch {
|
||||
try {
|
||||
val text = d.segments.joinToString("\n") {
|
||||
"[${fmtMs(it.first)}] ${it.second}"
|
||||
}
|
||||
val name = "meetrec-transcript-" +
|
||||
(item as? LibraryItem.Server)?.meta?.id
|
||||
.orEmpty() + ".txt"
|
||||
val path = withContext(Dispatchers.IO) {
|
||||
saveTextToDownloads(context, name, text)
|
||||
}
|
||||
Toast.makeText(context, "gespeichert: $path",
|
||||
Toast.LENGTH_LONG).show()
|
||||
} catch (e: Exception) {
|
||||
detailError = "save failed: ${e.message}"
|
||||
}
|
||||
}
|
||||
}) { Text("Datei speichern") }
|
||||
}
|
||||
LazyColumn(Modifier.fillMaxWidth().weight(1f)) {
|
||||
items(d.segments) { (startMs, text) ->
|
||||
Column(Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
||||
@@ -564,3 +621,35 @@ private fun MediaPlayer.stopSafely() {
|
||||
} catch (_: IllegalStateException) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyToClipboard(context: Context, text: String) {
|
||||
val cb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
cb.setPrimaryClip(ClipData.newPlainText("MeetRec", text))
|
||||
Toast.makeText(context, "kopiert", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun shareText(context: Context, text: String, title: String) {
|
||||
context.startActivity(
|
||||
Intent.createChooser(
|
||||
Intent(Intent.ACTION_SEND)
|
||||
.setType("text/plain")
|
||||
.putExtra(Intent.EXTRA_TEXT, text),
|
||||
title,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/** Writes text to Download/meetrec/<fileName>; returns the relative path. */
|
||||
private fun saveTextToDownloads(context: Context, fileName: String, text: String): String {
|
||||
val values = ContentValues().apply {
|
||||
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Downloads.MIME_TYPE, "text/plain")
|
||||
put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/meetrec")
|
||||
}
|
||||
val uri = context.contentResolver.insert(
|
||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI, values,
|
||||
) ?: throw java.io.IOException("could not create $fileName")
|
||||
context.contentResolver.openOutputStream(uri)?.use { it.write(text.toByteArray()) }
|
||||
?: throw java.io.IOException("could not write $fileName")
|
||||
return "${Environment.DIRECTORY_DOWNLOADS}/meetrec/$fileName"
|
||||
}
|
||||
Reference in New Issue
Block a user