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
|
package com.meetrec.android
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.media.MediaPlayer
|
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.clickable
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -387,14 +394,42 @@ private fun LibraryDetail(
|
|||||||
when {
|
when {
|
||||||
m.summary == "pending" -> Text("wird erstellt …",
|
m.summary == "pending" -> Text("wird erstellt …",
|
||||||
style = MaterialTheme.typography.bodySmall)
|
style = MaterialTheme.typography.bodySmall)
|
||||||
summaryText != null -> Text(
|
summaryText != null -> {
|
||||||
summaryText!!,
|
SelectionContainer {
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
Text(
|
||||||
modifier = Modifier
|
summaryText!!,
|
||||||
.fillMaxWidth()
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
.heightIn(max = 240.dp)
|
modifier = Modifier
|
||||||
.verticalScroll(rememberScrollState()),
|
.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") -> {
|
m.summary != null && m.summary.startsWith("error") -> {
|
||||||
Text(m.summary, color = MaterialTheme.colorScheme.error,
|
Text(m.summary, color = MaterialTheme.colorScheme.error,
|
||||||
style = MaterialTheme.typography.bodySmall)
|
style = MaterialTheme.typography.bodySmall)
|
||||||
@@ -481,8 +516,30 @@ private fun LibraryDetail(
|
|||||||
Text("No transcript available for this recording.",
|
Text("No transcript available for this recording.",
|
||||||
style = MaterialTheme.typography.bodySmall)
|
style = MaterialTheme.typography.bodySmall)
|
||||||
} else {
|
} else {
|
||||||
Text("Transcript (${d.segments.size} segments)",
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
style = MaterialTheme.typography.titleSmall)
|
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)) {
|
LazyColumn(Modifier.fillMaxWidth().weight(1f)) {
|
||||||
items(d.segments) { (startMs, text) ->
|
items(d.segments) { (startMs, text) ->
|
||||||
Column(Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
Column(Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
||||||
@@ -563,4 +620,36 @@ private fun MediaPlayer.stopSafely() {
|
|||||||
if (isPlaying) stop()
|
if (isPlaying) stop()
|
||||||
} catch (_: IllegalStateException) {
|
} 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