diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt index e3e4c818..29a4141a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt @@ -349,6 +349,7 @@ class ConfigProvider(private val context: Context) { } val radioOverlayEnabled: Flow = context.dataStore.data.map { it[radioOverlayKey] ?: false } + val radioVadEnabled: Flow = context.dataStore.data.map { (it[radioVadEnabledKey] ?: 0) != 0 } @Volatile private var radioOverlayCached = false @Volatile private var radioOverlayLoaded = false diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt index 47bd810e..ff6839a4 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt @@ -249,7 +249,7 @@ private fun RadioPttButton( } } -/* Единый выбор входа/выхода и переключатель плавающей PTT-кнопки. */ +/* Единый выбор входа/выхода, VAD и плавающая PTT-кнопка. */ @Composable private fun RadioSourceBar(viewModel: ChatViewModel) { val radioAuto by viewModel.radioAuto.collectAsState() @@ -260,6 +260,7 @@ private fun RadioSourceBar(viewModel: ChatViewModel) { val provider = ChatApplication.instance.configProvider val scope = rememberCoroutineScope() val overlayEnabled by provider.radioOverlayEnabled.collectAsState(initial = provider.getRadioOverlay()) + val vadEnabled by provider.radioVadEnabled.collectAsState(initial = provider.getInt("radio.vad_enabled") != 0) val overlayShown by RadioOverlayService.isShown.collectAsState() val overlayStatus by RadioOverlayService.status.collectAsState() @@ -299,6 +300,10 @@ private fun RadioSourceBar(viewModel: ChatViewModel) { } } VerticalDivider(modifier = Modifier.height(32.dp), color = Color(0xFF344550)) + RadioControlButton(null, stringResource(if (vadEnabled) R.string.disable_vad else R.string.enable_vad), + vadEnabled, text = stringResource(R.string.vad_button)) { + viewModel.setRadioVad(!vadEnabled) + } RadioControlButton(null, stringResource(R.string.floating_ptt), overlayShown, floating = true) { scope.launch { if (overlayEnabled && !overlayShown) RadioOverlayService.requestPermissions() @@ -317,7 +322,7 @@ private fun RadioSourceBar(viewModel: ChatViewModel) { @Composable private fun RadioControlButton( icon: ImageVector?, label: String, active: Boolean, - centerIcon: ImageVector? = null, bluetooth: Boolean = false, floating: Boolean = false, onClick: () -> Unit + centerIcon: ImageVector? = null, bluetooth: Boolean = false, floating: Boolean = false, text: String? = null, onClick: () -> Unit ) { IconToggleButton( checked = active, @@ -327,6 +332,10 @@ private fun RadioControlButton( containerColor = Color(0xFF24323D), contentColor = Color(0xFFAEBCC8), checkedContainerColor = Color(0xFF1F6FEB), checkedContentColor = Color.White) ) { + if (text != null) { + Text(text, fontSize = 12.sp, modifier = Modifier.semantics { contentDescription = label }) + return@IconToggleButton + } if (floating) { RadioFloatingIcon(label) return@IconToggleButton 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 cc851fc3..9165e4a2 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 @@ -567,24 +567,11 @@ private fun RadioSettingsTab() { modifier = Modifier.padding(16.dp).verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - var vadEnabled by remember { mutableStateOf(provider.getInt("radio.vad_enabled") != 0) } var vadThreshold by remember { mutableStateOf(provider.getInt("radio.vad_threshold")) } var vadHangover by remember { mutableStateOf(provider.getInt("radio.vad_hangover_ms")) } var vadCompMaxGain by remember { mutableStateOf(provider.getInt("radio.vad_compressor_max_gain_db")) } val vadActive by RadioAudioEngine.instance().vadActiveFlow.collectAsState() Text(stringResource(R.string.radio_vad), style = MaterialTheme.typography.titleMedium) - Row(verticalAlignment = Alignment.CenterVertically) { - Text(stringResource(R.string.enable_vad), modifier = Modifier.weight(1f)) - Switch(checked = vadEnabled, onCheckedChange = { enabled -> - vadEnabled = enabled - scope.launch { - provider.setValue("radio.vad_enabled", if (enabled) "1" else "0") - NativeLib.setChatSetting("radio_vad_enabled", if (enabled) "1" else "0") - if (enabled) RadioOverlayService.requestPermissions() else RadioOverlayService.sync(ChatApplication.instance) - RadioAudioEngine.instance().reconfigure() - } - }) - } Text(stringResource(if (vadActive) R.string.vad_active else R.string.vad_inactive), style = MaterialTheme.typography.bodySmall) Text(stringResource(R.string.vad_screen_hint), diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt index 4f570c4c..536487ca 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt @@ -661,6 +661,19 @@ class ChatViewModel : ViewModel() { radioAudio.talkEnd() } + /** Переключатель авто-PTT на панели рации: сохранить выбор и перенастроить захват. */ + fun setRadioVad(enabled: Boolean) { + if (!_radioActive.value) return + viewModelScope.launch { + val ctx = ChatApplication.instance + ctx.configProvider.setValue("radio.vad_enabled", if (enabled) "1" else "0") + NativeLib.setChatSetting("radio_vad_enabled", if (enabled) "1" else "0") + LogManager.addLog("INFO", "RadioAudio", "Panel VAD enabled=$enabled channel=$radioChannelId") + if (enabled) RadioOverlayService.requestPermissions() else RadioOverlayService.sync(ctx) + radioAudio.reconfigure() + } + } + /** Ручной выбор выхода рации (динамик/гарнитура) — синхронизирует вход. */ fun setRadioRoute(r: AudioRoute) { if (!_radioActive.value) return diff --git a/tools/chatgui-android/app/src/main/res/values-ru/strings.xml b/tools/chatgui-android/app/src/main/res/values-ru/strings.xml index 3dabce5f..2ab9b615 100644 --- a/tools/chatgui-android/app/src/main/res/values-ru/strings.xml +++ b/tools/chatgui-android/app/src/main/res/values-ru/strings.xml @@ -205,6 +205,8 @@ "Удалить всё" "Автоматическая передача по голосу (VAD)" "Включить VAD" + "Выключить VAD" + "VAD" "VAD активен: микрофон включён" "VAD неактивен" "Работает 15 минут при включённом экране. При следующем включении экрана запускается автоматически." diff --git a/tools/chatgui-android/app/src/main/res/values/strings.xml b/tools/chatgui-android/app/src/main/res/values/strings.xml index 4bb16e60..d138186a 100644 --- a/tools/chatgui-android/app/src/main/res/values/strings.xml +++ b/tools/chatgui-android/app/src/main/res/values/strings.xml @@ -205,6 +205,8 @@ "Delete all" "Automatic voice transmission (VAD)" "Enable VAD" + "Disable VAD" + "VAD" "VAD active: microphone is on" "VAD inactive" "Runs for 15 minutes while the screen is on. Starts automatically when the screen turns on again."