Load confirmed settings after pending changes and guard invalid audio preferences
This commit is contained in:
1 parent
48564e8ab3
commit
c5c4d0e588
2 files changed
+39
-18
No files matched your search
@@ -475,7 +475,7 @@ class ConfigProvider(private val context: Context) {
|
||||
fun getVoiceSettings(): VoiceSettings = runBlocking {
|
||||
val prefs = context.dataStore.data.first()
|
||||
val defaults = VoiceSettings()
|
||||
val settings = VoiceSettings(
|
||||
val raw = VoiceSettings(
|
||||
preset = prefs[chatOpusCodecPreset] ?: defaults.preset,
|
||||
compressorEnabled = (prefs[chatCompressorEnabled] ?: 1) != 0,
|
||||
maxGainDb = prefs[chatCompressorMaxGainDb] ?: defaults.maxGainDb,
|
||||
@@ -484,6 +484,10 @@ class ConfigProvider(private val context: Context) {
|
||||
riseRate = prefs[chatCompressorRiseRate] ?: defaults.riseRate,
|
||||
targetLevelDb = prefs[chatCompressorTargetLevelDb] ?: defaults.targetLevelDb
|
||||
)
|
||||
val settings = raw.copy(preset = raw.preset.coerceIn(0, 2), maxGainDb = raw.maxGainDb.coerceIn(0, 30),
|
||||
lookbackMs = raw.lookbackMs.coerceIn(50, 500), lookaheadMs = raw.lookaheadMs.coerceIn(20, 500),
|
||||
riseRate = raw.riseRate.coerceIn(2, 10), targetLevelDb = raw.targetLevelDb.coerceIn(-40, 0))
|
||||
if (raw != settings) LogManager.addLog("WARN", "AudioSettings", "Invalid saved voice settings normalized: $raw -> $settings")
|
||||
LogManager.addLog("INFO", "AudioSettings", "Loaded: $settings")
|
||||
settings
|
||||
}
|
||||
|
||||
+34
-17
@@ -76,6 +76,12 @@ import java.io.IOException
|
||||
private val LEVELS = listOf("DISABLED", "NONE", "ERROR", "WARN", "INFO", "DEBUG", "TRACE")
|
||||
private val PROTOCOLS = listOf("tcp", "udp")
|
||||
|
||||
private fun parseDebugPreferences(text: String): Map<String, String> = text.split(',').mapNotNull { entry ->
|
||||
val pair = entry.trim().split('=', limit = 2)
|
||||
if (pair.size == 2 && pair[0].isNotBlank() && pair[1].uppercase() in LEVELS) pair[0].trim() to pair[1].uppercase()
|
||||
else { if (entry.isNotBlank()) LogManager.addLog("WARN", "CFG", "Malformed debug category preference ignored"); null }
|
||||
}.toMap()
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(vm: ChatViewModel, onBack: () -> Unit, onExit: () -> Unit = {}) {
|
||||
@@ -88,7 +94,7 @@ fun SettingsScreen(vm: ChatViewModel, onBack: () -> Unit, onExit: () -> Unit = {
|
||||
var loadAttempt by remember { mutableIntStateOf(0) }
|
||||
LaunchedEffect(loadAttempt) {
|
||||
loadFailed = false
|
||||
try { withContext(Dispatchers.IO) { provider.allValues.first() }; loaded = true }
|
||||
try { changes.awaitIdle(); withContext(Dispatchers.IO) { provider.allValues.first() }; loaded = true }
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) { loadFailed = true; LogManager.addLog("ERROR", "Settings", "Settings load failed: $e") }
|
||||
}
|
||||
@@ -163,14 +169,18 @@ fun SettingsScreen(vm: ChatViewModel, onBack: () -> Unit, onExit: () -> Unit = {
|
||||
Tab(selected = tabIndex == idx, onClick = { tabIndex = idx }, text = { Text(stringResource(name), maxLines = 1) })
|
||||
}
|
||||
}
|
||||
when (tabIndex) {
|
||||
key(tabIndex) {
|
||||
var tabReady by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) { changes.awaitIdle(); tabReady = true }
|
||||
if (tabReady) when (tabIndex) {
|
||||
0 -> GeneralSettingsTab(onDirty = { dirty = it })
|
||||
1 -> AudioSettingsTab(vm)
|
||||
2 -> StorageSettingsTab()
|
||||
3 -> RadioSettingsTab()
|
||||
4 -> StreamSettingsTab(vm)
|
||||
4 -> StreamSettingsTab()
|
||||
5 -> DeveloperSettingsTab(vm, onDirty = { dirty = it }, onExit)
|
||||
6 -> BluetoothSettingsTab()
|
||||
} else CircularProgressIndicator(Modifier.padding(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,7 +188,7 @@ fun SettingsScreen(vm: ChatViewModel, onBack: () -> Unit, onExit: () -> Unit = {
|
||||
|
||||
/* Параметры RS сохраняются для следующей эпохи трансляции. */
|
||||
@Composable
|
||||
private fun StreamSettingsTab(vm: ChatViewModel) {
|
||||
private fun StreamSettingsTab() {
|
||||
val provider = ChatApplication.instance.configProvider
|
||||
var subblocks by rememberSaveable { mutableStateOf(provider.getInt("chat.stream_subblocks").toString()) }
|
||||
var redundant by rememberSaveable { mutableStateOf(provider.getInt("chat.stream_rs_redundant").toString()) }
|
||||
@@ -240,8 +250,11 @@ private fun GeneralSettingsTab(onDirty: (Boolean) -> Unit) {
|
||||
var localBroadcastsEnabled by remember { mutableStateOf(provider.getInt("chat.local_broadcasts_enabled") != 0) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
nodeName = provider.getString("node.name") ?: provider.getDeviceName()
|
||||
LogManager.addLog("INFO", "SETTINGS", "General settings opened")
|
||||
try {
|
||||
nodeName = provider.getString("node.name") ?: provider.getDeviceName()
|
||||
LogManager.addLog("INFO", "SETTINGS", "General settings opened")
|
||||
} catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) { provider.changes.reportFailure("load general settings", e) }
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
@@ -369,11 +382,7 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
standbyMinSleepSec = provider.getInt("chat.standby_min_sleep_sec").toString()
|
||||
standbyKeepaliveMs = provider.getInt("chat.standby_keepalive_ms").toString()
|
||||
val stored = provider.getString("debug.categories") ?: ""
|
||||
catLevels = stored.split(',').mapNotNull { entry ->
|
||||
val pair = entry.trim().split('=', limit = 2)
|
||||
if (pair.size == 2 && pair[0].isNotBlank() && pair[1].uppercase() in LEVELS) pair[0].trim() to pair[1].uppercase()
|
||||
else { if (entry.isNotBlank()) LogManager.addLog("WARN", "CFG", "Malformed debug category preference ignored"); null }
|
||||
}.toMap()
|
||||
catLevels = parseDebugPreferences(stored)
|
||||
try { servers = provider.loadServersFromConfig().await()
|
||||
serversLoaded = true
|
||||
} catch (e: CancellationException) { throw e
|
||||
@@ -425,10 +434,10 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
}
|
||||
}
|
||||
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}" }
|
||||
provider.changes.submit("debug category") {
|
||||
val updated = parseDebugPreferences(provider.getString("debug.categories").orEmpty()).toMutableMap()
|
||||
if (lvl.isEmpty()) updated.remove(cat) else updated[cat] = lvl
|
||||
val csv = updated.entries.joinToString(",") { "${it.key}=${it.value}" }
|
||||
provider.setValue("debug.categories", csv); catLevels = updated
|
||||
NativeLib.setDebugLevel(cat, lvl.ifEmpty { "NONE" })
|
||||
}
|
||||
@@ -815,10 +824,14 @@ private fun DeveloperSettingsTab(vm: ChatViewModel, onDirty: (Boolean) -> Unit,
|
||||
item(key = "exit-spacer") { Spacer(Modifier.height(8.dp)) }
|
||||
item(key = "exit") {
|
||||
Button(onClick = {
|
||||
provider.changes.submit("stop service and exit") {
|
||||
val request = provider.changes.submit("stop service and exit") {
|
||||
provider.setServiceEnabled(false)
|
||||
ChatApplication.instance.stopService(Intent(ChatApplication.instance, HeadlessService::class.java))
|
||||
onExit()
|
||||
}
|
||||
scope.launch {
|
||||
try { request.await(); onExit() }
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) { provider.changes.reportFailure("exit after service stop", e) }
|
||||
}
|
||||
}, modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)) {
|
||||
@@ -881,7 +894,7 @@ internal fun RadioAutomaticSettings(provider: ConfigProvider, compact: Boolean =
|
||||
}
|
||||
LaunchedEffect(resumed) {
|
||||
if (!resumed) { level = null; vadProbability = null; vadGain = null; return@LaunchedEffect }
|
||||
while (true) {
|
||||
try { while (true) {
|
||||
val status = NativeLib.radioInputStatus()
|
||||
active = engine.isActive()
|
||||
micPermission = MicrophonePermission.granted()
|
||||
@@ -891,6 +904,10 @@ internal fun RadioAutomaticSettings(provider: ConfigProvider, compact: Boolean =
|
||||
vadCompressorEnabled = status[6] != 0f
|
||||
blocked = status[2] != 0f
|
||||
delay(100)
|
||||
} } catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) {
|
||||
level = null; vadProbability = null; vadGain = null
|
||||
provider.changes.reportFailure("radio meter", e)
|
||||
}
|
||||
}
|
||||
if (!compact) Text(stringResource(R.string.radio_automatic), style = MaterialTheme.typography.titleMedium)
|
||||
|
||||
Reference in new issue
Block a user