From 40e6bd36c3a926f494d94ed45a143fbb28e90513 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 3 Oct 2026 14:08:17 +0300 Subject: [PATCH] android radio: release volume PTT on focus loss and option disable --- .../main/java/com/utun/chat/MainActivity.kt | 45 +++++++++++++------ .../java/com/utun/chat/data/ConfigProvider.kt | 15 +++++-- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt index b7610dd1..9ead8876 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.compose.LifecycleStartEffect +import androidx.lifecycle.lifecycleScope import com.utun.chat.data.Channel import com.utun.chat.data.IncomingCallManager import com.utun.chat.data.InviteData @@ -48,12 +49,14 @@ import com.utun.chat.data.ConversationTarget import com.utun.chat.data.NativeLib import com.utun.chat.data.RadioOverlayService import com.utun.chat.data.RadioAudioEngine +import com.utun.chat.data.RadioPttSource import com.utun.chat.headless.HeadlessService import com.utun.chat.ui.theme.VibeChatTheme import com.utun.chat.ui.components.ActiveCallBanner import com.utun.chat.ui.screens.* import com.utun.chat.viewmodel.ChatViewModel import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.collect class MainActivity : AppCompatActivity() { enum class Screen { ChannelList, Chat, MemberList, Settings, Logs, VideoRecord, PhotoRecord, Call, Dm } @@ -129,6 +132,11 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + lifecycleScope.launch { + ChatApplication.instance.configProvider.radioVolumePttEnabled.collect { enabled -> + if (!enabled) releaseVolumePtt("option-disabled") + } + } LogManager.addLog("INFO", "LANGUAGE", "UI language applied: selected=${AppLanguage.selectedTag().ifEmpty { "system" }} " + @@ -479,6 +487,7 @@ class MainActivity : AppCompatActivity() { } override fun onPause() { + releaseVolumePtt("activity-paused") RadioOverlayService.setActivityVisible(false) super.onPause() } @@ -544,31 +553,39 @@ class MainActivity : AppCompatActivity() { } /** Volume-кнопки (удержание) → PTT рации, при активной рации и включённой опции. */ - private var pttHoldCount = 0 + private val pttVolumeKeys = mutableSetOf() + + private fun releaseVolumePtt(reason: String) { + if (pttVolumeKeys.isEmpty()) return + pttVolumeKeys.clear() + RadioAudioEngine.instance().cancelTalk(RadioPttSource.VOLUME) + LogManager.addLog("INFO", "PTT", "Volume hold cancelled: $reason") + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + if (!hasFocus) releaseVolumePtt("window-focus-lost") + super.onWindowFocusChanged(hasFocus) + } override fun dispatchKeyEvent(event: KeyEvent): Boolean { val isVol = event.keyCode == KeyEvent.KEYCODE_VOLUME_UP || event.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN val pttOn = ChatApplication.instance.configProvider.getRadioVolumePtt() && RadioAudioEngine.instance().isActive() - if (!pttOn || !isVol) { - pttHoldCount = 0 - return super.dispatchKeyEvent(event) - } + if (!pttOn) releaseVolumePtt("disabled-or-radio-inactive") + if (!pttOn || !isVol) return super.dispatchKeyEvent(event) when (event.action) { KeyEvent.ACTION_DOWN -> { - if (event.repeatCount == 0) { - if (pttHoldCount == 0 && !RadioAudioEngine.instance().talkBegin()) return true - pttHoldCount++ - LogManager.addLog("DEBUG", "PTT", "key=${event.keyCode} down hold=$pttHoldCount") + if (event.repeatCount == 0 && event.keyCode !in pttVolumeKeys) { + if (pttVolumeKeys.isEmpty() && !RadioAudioEngine.instance().talkBegin(RadioPttSource.VOLUME)) return true + pttVolumeKeys.add(event.keyCode) + LogManager.addLog("DEBUG", "PTT", "key=${event.keyCode} down held=$pttVolumeKeys") } return true } KeyEvent.ACTION_UP -> { - if (pttHoldCount > 0) { - pttHoldCount-- - if (pttHoldCount == 0) RadioAudioEngine.instance().talkEnd() - } - LogManager.addLog("DEBUG", "PTT", "key=${event.keyCode} up hold=$pttHoldCount") + if (pttVolumeKeys.remove(event.keyCode) && pttVolumeKeys.isEmpty()) + RadioAudioEngine.instance().talkEnd(RadioPttSource.VOLUME) + LogManager.addLog("DEBUG", "PTT", "key=${event.keyCode} up held=$pttVolumeKeys cancelled=${event.isCanceled}") return true } } 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 29a4141a..23721b92 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 @@ -17,6 +17,7 @@ import java.net.Inet6Address import java.net.InetAddress import java.net.NetworkInterface import java.io.File +import java.io.IOException import java.util.UUID import java.util.concurrent.ThreadLocalRandom @@ -330,7 +331,7 @@ class ConfigProvider(private val context: Context) { /* ── Рация: volume-кнопки PTT и оверлей ── */ /* Кэш быстрых настроек рации: чтение не блокирует главный поток. - * Ленивая загрузка один раз, дальше — из памяти; set обновляет кэш сразу. */ + * Ленивая загрузка один раз, дальше — из памяти; set обновляет кэш после сохранения. */ @Volatile private var radioVolumePttCached = false @Volatile private var radioVolumePttLoaded = false @@ -343,11 +344,17 @@ class ConfigProvider(private val context: Context) { } suspend fun setRadioVolumePtt(enabled: Boolean) { - radioVolumePttCached = enabled - radioVolumePttLoaded = true - context.dataStore.edit { prefs -> prefs[radioVolumePttKey] = enabled } + try { + context.dataStore.edit { prefs -> prefs[radioVolumePttKey] = enabled } + radioVolumePttCached = enabled + radioVolumePttLoaded = true + LogManager.addLog("INFO", "PTT", "Volume PTT enabled=$enabled") + } catch (e: IOException) { + LogManager.addLog("ERROR", "PTT", "Cannot save volume PTT enabled=$enabled: $e") + } } + val radioVolumePttEnabled: Flow = context.dataStore.data.map { it[radioVolumePttKey] ?: false } val radioOverlayEnabled: Flow = context.dataStore.data.map { it[radioOverlayKey] ?: false } val radioVadEnabled: Flow = context.dataStore.data.map { (it[radioVadEnabledKey] ?: 0) != 0 }