From eb837206c3cfecbeff37e78bfaddf3cb7ffcfd57 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 10:57:28 +0100 Subject: [PATCH] Own radio settings changes and preserve observed state after IO failures --- .../utun/chat/ui/screens/SettingsScreen.kt | 72 +++++++++++-------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt index 4c43446c..2567aff4 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt @@ -332,8 +332,8 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit, var autoConnect by remember { mutableStateOf(true) } val localSocketStates by vm.localSockets.collectAsState() - val serviceEnabled by provider.serviceEnabledFlow.collectAsState(initial = true) - val startOnBoot by provider.startOnBootFlow.collectAsState(initial = true) + val serviceEnabled by remember { provider.changes.observe("service preference", provider.serviceEnabledFlow) }.collectAsState(initial = true) + val startOnBoot by remember { provider.changes.observe("boot preference", provider.startOnBootFlow) }.collectAsState(initial = true) var savingStartOnBoot by remember { mutableStateOf(false) } var startOnBootError by remember { mutableStateOf(false) } @@ -387,7 +387,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit, autoConnect = provider.getInt("chat.group_autoconnect") != 0 } - val save = { s: List -> + val save: (List) -> Unit = { s -> onDirty(true) servers = s val revision = ++serverSaveRevision @@ -402,7 +402,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit, } } } - val switchMode = { mode: String -> + val switchMode: (String) -> Unit = { mode -> onDirty(true) provider.changes.submit("connection mode") { try { @@ -418,13 +418,13 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit, } } } - val setDebug = { level: String -> + val setDebug: (String) -> Unit = { level -> provider.changes.submit("debug level") { provider.setValue("debug.console_level", level) NativeLib.setDebugLevel("", level); debugLevel = level } } - val setCatLevel = { cat: String, lvl: String -> + val setCatLevel: (String, String) -> Unit = { cat, lvl -> val updated = catLevels.toMutableMap() if (lvl == "") updated.remove(cat) else updated[cat] = lvl val csv = updated.entries.joinToString(",") { "${it.key}=${it.value}" } @@ -859,7 +859,8 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit, internal fun RadioAutomaticSettings(provider: ConfigProvider, compact: Boolean = false) { val engine = remember { RadioAudioEngine.instance() } val scope = rememberCoroutineScope() - val saved by provider.radioAutoSettings.collectAsState(initial = remember { provider.getRadioAutoSettings() }) + val saved by remember { provider.changes.observe("radio automatic preferences", provider.radioAutoSettings) } + .collectAsState(initial = remember { provider.getRadioAutoSettings() }) var draft by remember(saved) { mutableStateOf(saved) } var saving by remember { mutableStateOf(false) } var saveError by remember { mutableStateOf(false) } @@ -971,7 +972,7 @@ internal fun RadioAutomaticSettings(provider: ConfigProvider, compact: Boolean = val next = draft val restart = next.enabled && !saved.enabled saving = true; saveError = false - scope.launch { + provider.changes.submit("radio automatic transmission") { try { provider.saveRadioAutoSettings(next) engine.reconfigure(restartAuto = restart) @@ -980,6 +981,7 @@ internal fun RadioAutomaticSettings(provider: ConfigProvider, compact: Boolean = catch (e: Exception) { saveError = true LogManager.addLog("ERROR", "radio", "Cannot save automatic transmission settings: $e") + throw e } finally { saving = false } } }) { Text(stringResource(R.string.radio_auto_apply)) } @@ -1005,18 +1007,20 @@ private fun RadioSettingsTab() { HorizontalDivider(Modifier.padding(vertical = 4.dp)) /* ── Volume-кнопки PTT ── */ - val volumePtt by provider.radioVolumePttEnabled.collectAsState(initial = provider.getRadioVolumePtt()) + val volumePtt by remember { provider.changes.observe("volume PTT preference", provider.radioVolumePttEnabled) } + .collectAsState(initial = provider.getRadioVolumePtt()) Row(verticalAlignment = Alignment.CenterVertically) { Text(stringResource(R.string.volume_ptt), modifier = Modifier.weight(1f)) Switch(checked = volumePtt, onCheckedChange = { - scope.launch { provider.setRadioVolumePtt(it) } + provider.changes.submit("volume PTT") { provider.setRadioVolumePtt(it) } }) } HorizontalDivider(Modifier.padding(vertical = 4.dp)) /* ── Оверлей PTT поверх приложений ── */ - val overlayEnabled by provider.radioOverlayEnabled.collectAsState(initial = provider.getRadioOverlay()) + val overlayEnabled by remember { provider.changes.observe("overlay preference", provider.radioOverlayEnabled) } + .collectAsState(initial = provider.getRadioOverlay()) val overlayShown by RadioOverlayService.isShown.collectAsState() val overlayStatus by RadioOverlayService.status.collectAsState() Row(verticalAlignment = Alignment.CenterVertically) { @@ -1030,7 +1034,7 @@ private fun RadioSettingsTab() { ) } Switch(checked = overlayEnabled, onCheckedChange = { enabled -> - scope.launch { RadioOverlayService.setEnabled(enabled) } + provider.changes.submit("radio overlay") { RadioOverlayService.setEnabled(enabled) } }) } @@ -1050,9 +1054,9 @@ private fun RadioSettingsTab() { ExposedDropdownMenu(micExpanded, onDismissRequest = { micExpanded = false }) { micSources.forEachIndexed { i, n -> DropdownMenuItem(text = { Text(n) }, onClick = { - micSource = i - scope.launch { + provider.changes.submit("radio microphone source") { provider.setRadioMicSource(i) + micSource = i RadioAudioEngine.instance().reconfigure() } micExpanded = false @@ -1072,9 +1076,9 @@ private fun RadioSettingsTab() { style = MaterialTheme.typography.bodySmall) } Switch(checked = captureStereo, onCheckedChange = { - captureStereo = it - scope.launch { + provider.changes.submit("radio stereo capture") { provider.setRadioCaptureStereo(it) + captureStereo = it NativeLib.setChatSetting("radio_capture_stereo", if (it) "1" else "0") RadioAudioEngine.instance().reconfigure() } @@ -1090,20 +1094,24 @@ private fun RadioSettingsTab() { Row(verticalAlignment = Alignment.CenterVertically) { Text(stringResource(R.string.enable_compressor), modifier = Modifier.weight(1f)) Switch(checked = radioCompOn, onCheckedChange = { - radioCompOn = it - scope.launch { provider.setValue("radio.compressor_enabled", if (it) "1" else "0") } - NativeLib.setChatSetting("radio_compressor_enabled", if (it) "1" else "0") - RadioAudioEngine.instance().reconfigure() + provider.changes.submit("radio compressor") { + provider.setValue("radio.compressor_enabled", if (it) "1" else "0"); radioCompOn = it + if (NativeLib.isReady()) NativeLib.setChatSetting("radio_compressor_enabled", if (it) "1" else "0") + RadioAudioEngine.instance().reconfigure() + } }) } LabeledSlider(stringResource(R.string.max_gain), radioCompMaxGain.toFloat(), 1f..45f, 44, "${radioCompMaxGain} dB", { v -> radioCompMaxGain = v.toInt() - NativeLib.setChatSetting("radio_compressor_max_gain_db", radioCompMaxGain.toString()) }, onValueChangeFinished = { - scope.launch { provider.setValue("radio.compressor_max_gain_db", radioCompMaxGain.toString()) } - RadioAudioEngine.instance().reconfigure() + val gain = radioCompMaxGain + provider.changes.submit("radio compressor gain") { + provider.setValue("radio.compressor_max_gain_db", gain.toString()) + if (NativeLib.isReady()) NativeLib.setChatSetting("radio_compressor_max_gain_db", gain.toString()) + RadioAudioEngine.instance().reconfigure() + } }) HorizontalDivider(Modifier.padding(vertical = 4.dp)) @@ -1123,10 +1131,11 @@ private fun RadioSettingsTab() { ExposedDropdownMenu(opusModeExpanded, onDismissRequest = { opusModeExpanded = false }) { opusModes.forEachIndexed { i, n -> DropdownMenuItem(text = { Text(n) }, onClick = { - opusMode = i - scope.launch { provider.setValue("radio.opus_mode", i.toString()) } - NativeLib.setChatSetting("radio_opus_mode", i.toString()) - RadioAudioEngine.instance().reconfigure() + provider.changes.submit("radio Opus mode") { + provider.setValue("radio.opus_mode", i.toString()); opusMode = i + if (NativeLib.isReady()) NativeLib.setChatSetting("radio_opus_mode", i.toString()) + RadioAudioEngine.instance().reconfigure() + } opusModeExpanded = false }) } @@ -1139,10 +1148,11 @@ private fun RadioSettingsTab() { ExposedDropdownMenu(opusBitrateExpanded, onDismissRequest = { opusBitrateExpanded = false }) { opusBitrates.forEach { b -> DropdownMenuItem(text = { Text(stringResource(R.string.bitrate_kbps, b)) }, onClick = { - opusBitrate = b - scope.launch { provider.setValue("radio.opus_bitrate", b.toString()) } - NativeLib.setChatSetting("radio_opus_bitrate", b.toString()) - RadioAudioEngine.instance().reconfigure() + provider.changes.submit("radio Opus bitrate") { + provider.setValue("radio.opus_bitrate", b.toString()); opusBitrate = b + if (NativeLib.isReady()) NativeLib.setChatSetting("radio_opus_bitrate", b.toString()) + RadioAudioEngine.instance().reconfigure() + } opusBitrateExpanded = false }) }