Release GPS2Audio v2.5.44 POI category fix

This commit is contained in:
Perplexity Computer
2026-05-30 05:42:44 +00:00
parent 18ea77b21d
commit 014e6c1bf1
100 changed files with 28584 additions and 1083 deletions
@@ -21,7 +21,8 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.FiberManualRecord
import androidx.compose.material.icons.filled.LibraryMusic
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.MicOff
import androidx.compose.material.icons.filled.Refresh
@@ -41,7 +42,8 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -61,30 +63,33 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import de.waypointaudio.R
import de.waypointaudio.data.AudioRoutingSettings
import de.waypointaudio.service.AudioDeviceItem
import de.waypointaudio.data.PttRecordingFormat
import de.waypointaudio.service.LivePttManager
/**
* Karte für Live / PTT auf dem Hauptbildschirm.
* Platzierung: zwischen Atmo-Mini-Player und Wegpunkt-Tracks-Überschrift.
*
* Features:
* - PTT starten / stoppen (Toggle-Button)
* - Statusanzeige (Bereit / Mikrofon aktiv / Berechtigung fehlt)
* - Echo-Warnung bei fehlendem Headset
* - Button zum Öffnen der Audio-Geräte-Einstellungen
* - Gerätewahl-Kurzanzeige (gewählte Geräte-Namen)
* v2.1.0:
* - Toggle „Live-Ausgabe" und „Aufnahme speichern" (mit Format M4A/AAC).
* - Aufnahme-Indikator (rotes Punkt) im aktiven Zustand.
* - Knopf „Aufnahmen…" öffnet die Bibliothek; Post-Aufnahme-Dialog erscheint
* automatisch nach jeder gespeicherten Aufnahme.
*
* @param pttManager Singleton-Manager
* @param onOpenRecordings Callback um die Aufnahme-Bibliothek zu öffnen
*/
@Composable
fun LivePttCard(
pttManager: LivePttManager,
onOpenRecordings: () -> Unit = {},
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val pttActive by pttManager.pttActive.collectAsState()
val routing by pttManager.routingSettings.collectAsState()
val inputDevices by pttManager.inputDevices.collectAsState()
val statusMsg by pttManager.statusMessage.collectAsState()
val pttActive by pttManager.pttActive.collectAsState()
val recordingActive by pttManager.recordingActive.collectAsState()
val routing by pttManager.routingSettings.collectAsState()
val inputDevices by pttManager.inputDevices.collectAsState()
val statusMsg by pttManager.statusMessage.collectAsState()
var hasMicPermission by remember {
mutableStateOf(
@@ -106,12 +111,10 @@ fun LivePttCard(
}
}
// Geräte laden wenn Karte angezeigt wird
LaunchedEffect(Unit) {
pttManager.refreshDevices()
}
// Farbanimation für PTT-Button
val pttContainerColor by animateColorAsState(
targetValue = if (pttActive)
MaterialTheme.colorScheme.error
@@ -121,24 +124,26 @@ fun LivePttCard(
label = "pttColor"
)
// Anzeige-Name des gewählten Eingabegeräts
val inputDeviceName = remember(routing.selectedInputDeviceId, inputDevices) {
if (routing.selectedInputDeviceId == null) null
else inputDevices.firstOrNull { it.id == routing.selectedInputDeviceId }?.displayName
}
// v2.5.1 — Card mit sanften Cornern und größerem Innenabstand für
// einen ruhigeren Look in der GPS2Audio-Farbwelt.
Card(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 2.dp),
.padding(horizontal = 12.dp, vertical = 4.dp),
shape = androidx.compose.foundation.shape.RoundedCornerShape(14.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) {
Column(modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)) {
// ── Kopfzeile: Titel + Geräte-Button ─────────────────────────────
// ── Kopfzeile: Titel + Aufnahmen-Button + Einstellungen-Button ───
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
@@ -158,7 +163,13 @@ fun LivePttCard(
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.weight(1f)
)
// Gerätewahl-Button
IconButton(onClick = onOpenRecordings) {
Icon(
Icons.Filled.LibraryMusic,
contentDescription = stringResource(R.string.ptt_recordings_button),
tint = MaterialTheme.colorScheme.primary
)
}
IconButton(
onClick = {
pttManager.refreshDevices()
@@ -178,13 +189,13 @@ fun LivePttCard(
// ── Status-Zeile ──────────────────────────────────────────────────
val statusText = when {
!hasMicPermission -> stringResource(R.string.ptt_status_permission_missing)
recordingActive -> stringResource(R.string.ptt_recording_status)
pttActive -> stringResource(R.string.ptt_status_active)
statusMsg != null -> statusMsg!!
else -> stringResource(R.string.ptt_status_ready)
}
Row(verticalAlignment = Alignment.CenterVertically) {
// Status-Indikator (runder Punkt)
Box(
modifier = Modifier
.size(8.dp)
@@ -205,9 +216,17 @@ fun LivePttCard(
else -> MaterialTheme.colorScheme.onSurfaceVariant
}
)
if (recordingActive) {
Spacer(Modifier.width(8.dp))
Icon(
imageVector = Icons.Filled.FiberManualRecord,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(12.dp)
)
}
}
// Gewähltes Gerät anzeigen (falls nicht Systemstandard)
if (inputDeviceName != null) {
Spacer(Modifier.height(4.dp))
Text(
@@ -217,7 +236,6 @@ fun LivePttCard(
)
}
// Berechtigung verweigert Hinweis
if (showPermissionDeniedHint) {
Spacer(Modifier.height(4.dp))
Text(
@@ -227,6 +245,19 @@ fun LivePttCard(
)
}
// Mode summary (Live / Aufnahme / beides)
Spacer(Modifier.height(4.dp))
val modeText = when {
routing.liveOutputEnabled && routing.recordingEnabled -> "Live + Aufnahme"
routing.recordingEnabled -> "Nur Aufnahme"
else -> "Nur Live"
}
Text(
text = modeText,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.outline
)
Spacer(Modifier.height(6.dp))
// ── PTT-Hauptbutton ───────────────────────────────────────────────
@@ -257,10 +288,9 @@ fun LivePttCard(
)
}
// Echo-Warnung (immer sichtbar wenn kein Headset konfiguriert)
val noHeadset = routing.selectedOutputDeviceId == null &&
routing.selectedInputDeviceId == null
if (noHeadset) {
if (noHeadset && routing.liveOutputEnabled) {
Spacer(Modifier.height(6.dp))
Text(
text = stringResource(R.string.ptt_echo_warning),
@@ -271,9 +301,8 @@ fun LivePttCard(
}
}
// ── Audio-Geräte-Dialog ───────────────────────────────────────────────────
if (showDeviceDialog) {
AudioDeviceDialog(
PttSettingsDialog(
pttManager = pttManager,
onDismiss = { showDeviceDialog = false }
)
@@ -281,12 +310,12 @@ fun LivePttCard(
}
// ─────────────────────────────────────────────────────────────────────────────
// Audio-Geräte-Dialog
// PTT-Einstellungen Dialog (Live/Aufnahme + Geräte)
// ─────────────────────────────────────────────────────────────────────────────
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AudioDeviceDialog(
fun PttSettingsDialog(
pttManager: LivePttManager,
onDismiss: () -> Unit
) {
@@ -294,9 +323,12 @@ fun AudioDeviceDialog(
val outputDevices by pttManager.outputDevices.collectAsState()
val routing by pttManager.routingSettings.collectAsState()
// Lokale Auswahl-States
var selectedInputId by remember(routing) { mutableStateOf(routing.selectedInputDeviceId) }
var selectedOutputId by remember(routing) { mutableStateOf(routing.selectedOutputDeviceId) }
var liveEnabled by remember(routing) { mutableStateOf(routing.liveOutputEnabled) }
var recordEnabled by remember(routing) { mutableStateOf(routing.recordingEnabled) }
var format by remember(routing) { mutableStateOf(routing.recordingFormat) }
var validationHint by remember { mutableStateOf<String?>(null) }
var inputExpanded by remember { mutableStateOf(false) }
var outputExpanded by remember { mutableStateOf(false) }
@@ -314,10 +346,113 @@ fun AudioDeviceDialog(
text = {
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// ── Live / Aufnahme-Modus ─────────────────────────────────────
Text(
text = stringResource(R.string.ptt_settings_section),
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.primary
)
// ── Refresh-Button ────────────────────────────────────────────
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = stringResource(R.string.ptt_live_output_toggle),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold
)
Text(
text = stringResource(R.string.ptt_live_output_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Switch(
checked = liveEnabled,
onCheckedChange = { v ->
liveEnabled = v
validationHint = null
if (!v && !recordEnabled) {
liveEnabled = true
validationHint = "Mindestens eine Option aktiv lassen."
}
}
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = stringResource(R.string.ptt_recording_toggle),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold
)
Text(
text = stringResource(R.string.ptt_recording_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Switch(
checked = recordEnabled,
onCheckedChange = { v ->
recordEnabled = v
validationHint = null
if (!v && !liveEnabled) {
recordEnabled = true
validationHint = "Mindestens eine Option aktiv lassen."
}
}
)
}
validationHint?.let { hint ->
Text(
text = hint,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.error
)
}
// Format (M4A only, WAV disabled)
if (recordEnabled) {
Text(
text = stringResource(R.string.ptt_format_label),
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.primary
)
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(
selected = format == PttRecordingFormat.M4A_AAC,
onClick = { format = PttRecordingFormat.M4A_AAC }
)
Text(stringResource(R.string.ptt_format_m4a))
}
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(
selected = false,
onClick = { /* WAV deaktiviert in v2.1.0 */ },
enabled = false
)
Text(
stringResource(R.string.ptt_format_wav),
color = MaterialTheme.colorScheme.outline
)
}
}
HorizontalDivider()
// ── Geräteauswahl ─────────────────────────────────────────────
TextButton(
onClick = { pttManager.refreshDevices() },
modifier = Modifier.align(Alignment.End)
@@ -329,7 +464,6 @@ fun AudioDeviceDialog(
style = MaterialTheme.typography.labelMedium)
}
// ── Eingabegerät (Mikrofon) ───────────────────────────────────
Text(
text = stringResource(R.string.audio_input_device_label),
style = MaterialTheme.typography.labelMedium,
@@ -365,7 +499,6 @@ fun AudioDeviceDialog(
expanded = inputExpanded,
onDismissRequest = { inputExpanded = false }
) {
// Systemstandard
DropdownMenuItem(
text = { Text(stringResource(R.string.ptt_device_system_default)) },
onClick = {
@@ -387,7 +520,6 @@ fun AudioDeviceDialog(
}
}
// ── Ausgabegerät (Lautsprecher) ───────────────────────────────
Text(
text = stringResource(R.string.audio_output_device_label),
style = MaterialTheme.typography.labelMedium,
@@ -423,7 +555,6 @@ fun AudioDeviceDialog(
expanded = outputExpanded,
onDismissRequest = { outputExpanded = false }
) {
// Systemstandard
DropdownMenuItem(
text = { Text(stringResource(R.string.ptt_device_system_default)) },
onClick = {
@@ -445,7 +576,6 @@ fun AudioDeviceDialog(
}
}
// ── Hinweis zu Geräte-IDs ─────────────────────────────────────
HorizontalDivider()
Text(
text = stringResource(R.string.audio_devices_hint),
@@ -459,7 +589,10 @@ fun AudioDeviceDialog(
pttManager.saveRoutingSettings(
AudioRoutingSettings(
selectedInputDeviceId = selectedInputId,
selectedOutputDeviceId = selectedOutputId
selectedOutputDeviceId = selectedOutputId,
liveOutputEnabled = liveEnabled,
recordingEnabled = recordEnabled,
recordingFormat = format
)
)
onDismiss()