package de.waypointaudio.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import de.waypointaudio.R import de.waypointaudio.data.BackgroundReliabilityStore import de.waypointaudio.util.AppVersion import de.waypointaudio.util.BackgroundReliability import kotlinx.coroutines.launch /** * v2.5.13 — Dezenter Hinweis "Hintergrundwiedergabe absichern". * * Anzeigeregeln: * - Nur sichtbar, wenn die App NICHT von Akkuoptimierungen ausgenommen ist * (sonst gibt es nichts zu tun) UND der Nutzer den Hinweis für diese * Version noch nicht weggeklickt hat. * - Auf Xiaomi/Redmi/Poco wird die spezifische Schrittliste (Akku, Autostart, * Recents-Schloss, Notifications) gezeigt — sonst eine generische Variante. * - "Verstanden" merkt den aktuellen versionCode, "Nicht mehr anzeigen" ist * eine Checkbox, weil so vorhandene Dialoge in der App (Versionshinweis, * Terms) bereits funktionieren — konsistente UX. * - Blockiert die Wiedergabe nicht: der Nutzer kann den Dialog jederzeit * schließen, und das Playback läuft währenddessen weiter. */ @Composable fun BackgroundReliabilityDialogHost( /** * Auslöser, ob der Hinweis überhaupt geprüft werden soll. Wird die * Komponente immer in der Composition gehalten, übernimmt sie selbst * die Anzeige-Entscheidung; das ist gewollt einfach. */ triggerCheck: Boolean, onDismissed: () -> Unit = {} ) { val context = LocalContext.current val store = remember(context) { BackgroundReliabilityStore(context.applicationContext) } val versionInfo = remember(context) { AppVersion.get(context) } val dismissedCode by store.dismissedVersionCode.collectAsState(initial = -1L) val scope = rememberCoroutineScope() var visible by remember { mutableStateOf(false) } var dontShowAgain by remember { mutableStateOf(false) } LaunchedEffect(triggerCheck, dismissedCode, versionInfo.versionCode) { if (!triggerCheck) return@LaunchedEffect if (dismissedCode < 0L) return@LaunchedEffect if (dismissedCode >= versionInfo.versionCode) return@LaunchedEffect // Bereits ausgenommen → kein Bedarf, Nutzer mit dem Hinweis zu nerven. if (BackgroundReliability.isIgnoringBatteryOptimizations(context)) return@LaunchedEffect visible = true } if (!visible) return BackgroundReliabilityDialog( isXiaomi = BackgroundReliability.isXiaomiFamily(), isOptimizationsIgnored = BackgroundReliability.isIgnoringBatteryOptimizations(context), dontShowAgain = dontShowAgain, onDontShowAgainChanged = { dontShowAgain = it }, onOpenBattery = { BackgroundReliability.openBatteryOptimizationSettings(context) }, onOpenAppDetails = { BackgroundReliability.openAppDetailsSettings(context) }, onOpenNotifications = { BackgroundReliability.openNotificationSettings(context) }, onOpenXiaomiAutostart = { BackgroundReliability.openXiaomiAutostartSettings(context) }, onDismiss = { if (dontShowAgain) { scope.launch { store.setDismissedVersionCode(versionInfo.versionCode) } } visible = false onDismissed() } ) } /** * Manuell aufrufbarer Dialog für den Einstellungen/About-Bereich. Ignoriert * den "für Version verstanden"-Speicher und zeigt den Hinweis immer. */ @Composable fun BackgroundReliabilityManualDialog( onClose: () -> Unit ) { val context = LocalContext.current BackgroundReliabilityDialog( isXiaomi = BackgroundReliability.isXiaomiFamily(), isOptimizationsIgnored = BackgroundReliability.isIgnoringBatteryOptimizations(context), dontShowAgain = false, onDontShowAgainChanged = {}, showDontShowAgain = false, onOpenBattery = { BackgroundReliability.openBatteryOptimizationSettings(context) }, onOpenAppDetails = { BackgroundReliability.openAppDetailsSettings(context) }, onOpenNotifications = { BackgroundReliability.openNotificationSettings(context) }, onOpenXiaomiAutostart = { BackgroundReliability.openXiaomiAutostartSettings(context) }, onDismiss = onClose ) } @Composable private fun BackgroundReliabilityDialog( isXiaomi: Boolean, isOptimizationsIgnored: Boolean, dontShowAgain: Boolean, onDontShowAgainChanged: (Boolean) -> Unit, onOpenBattery: () -> Unit, onOpenAppDetails: () -> Unit, onOpenNotifications: () -> Unit, onOpenXiaomiAutostart: () -> Unit, onDismiss: () -> Unit, showDontShowAgain: Boolean = true ) { AlertDialog( onDismissRequest = onDismiss, title = { Text( text = stringResource(R.string.bg_reliability_title), style = MaterialTheme.typography.headlineSmall ) }, text = { Column( modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( text = stringResource(R.string.bg_reliability_intro), style = MaterialTheme.typography.bodyMedium ) Card( colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surfaceVariant ) ) { Box(Modifier.padding(12.dp)) { Text( text = if (isOptimizationsIgnored) { stringResource(R.string.bg_reliability_status_ok) } else { stringResource(R.string.bg_reliability_status_optimized) }, style = MaterialTheme.typography.bodySmall ) } } if (isXiaomi) { Text( text = stringResource(R.string.bg_reliability_xiaomi_heading), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold ) BulletLine(stringResource(R.string.bg_reliability_xiaomi_item_battery)) BulletLine(stringResource(R.string.bg_reliability_xiaomi_item_autostart)) BulletLine(stringResource(R.string.bg_reliability_xiaomi_item_lock)) BulletLine(stringResource(R.string.bg_reliability_xiaomi_item_notify)) } else { Text( text = stringResource(R.string.bg_reliability_generic_heading), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold ) BulletLine(stringResource(R.string.bg_reliability_generic_item_battery)) BulletLine(stringResource(R.string.bg_reliability_generic_item_notify)) } Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { OutlinedButton( onClick = onOpenBattery, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.bg_reliability_btn_battery)) } OutlinedButton( onClick = onOpenNotifications, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.bg_reliability_btn_notify)) } OutlinedButton( onClick = onOpenAppDetails, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.bg_reliability_btn_app)) } if (isXiaomi) { OutlinedButton( onClick = onOpenXiaomiAutostart, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.bg_reliability_btn_autostart)) } } } if (showDontShowAgain) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .padding(top = 4.dp) ) { Checkbox( checked = dontShowAgain, onCheckedChange = onDontShowAgainChanged ) Text( text = stringResource(R.string.bg_reliability_dont_show_again), style = MaterialTheme.typography.bodyMedium ) } } } }, confirmButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.bg_reliability_understood)) } } ) } @Composable private fun BulletLine(text: String) { Row(verticalAlignment = Alignment.Top) { Text( text = "• ", style = MaterialTheme.typography.bodyMedium ) Text( text = text, style = MaterialTheme.typography.bodyMedium ) } }