|
|
|
@ -14,12 +14,14 @@ import androidx.compose.ui.Alignment |
|
|
|
import androidx.compose.ui.Modifier |
|
|
|
import androidx.compose.ui.Modifier |
|
|
|
import androidx.compose.ui.graphics.Color |
|
|
|
import androidx.compose.ui.graphics.Color |
|
|
|
import androidx.compose.ui.text.input.KeyboardType |
|
|
|
import androidx.compose.ui.text.input.KeyboardType |
|
|
|
|
|
|
|
import androidx.compose.ui.text.style.TextDecoration |
|
|
|
import androidx.compose.ui.unit.dp |
|
|
|
import androidx.compose.ui.unit.dp |
|
|
|
import com.utun.chat.ChatApplication |
|
|
|
import com.utun.chat.ChatApplication |
|
|
|
import com.utun.chat.data.NativeLib |
|
|
|
import com.utun.chat.data.NativeLib |
|
|
|
import com.utun.chat.data.NotificationSound |
|
|
|
import com.utun.chat.data.NotificationSound |
|
|
|
import com.utun.chat.data.IncomingCallManager |
|
|
|
import com.utun.chat.data.IncomingCallManager |
|
|
|
import com.utun.chat.data.RadioOverlayService |
|
|
|
import com.utun.chat.data.RadioOverlayService |
|
|
|
|
|
|
|
import com.utun.chat.data.RadioAudioEngine |
|
|
|
import com.utun.chat.data.LogManager |
|
|
|
import com.utun.chat.data.LogManager |
|
|
|
import com.utun.chat.data.ServerEntry |
|
|
|
import com.utun.chat.data.ServerEntry |
|
|
|
import com.utun.chat.data.LocalSocketState |
|
|
|
import com.utun.chat.data.LocalSocketState |
|
|
|
@ -56,7 +58,7 @@ fun SettingsScreen(vm: ChatViewModel, onBack: () -> Unit, onExit: () -> Unit = { |
|
|
|
var dirty by remember { mutableStateOf(false) } |
|
|
|
var dirty by remember { mutableStateOf(false) } |
|
|
|
|
|
|
|
|
|
|
|
var tabIndex by remember { mutableStateOf(0) } |
|
|
|
var tabIndex by remember { mutableStateOf(0) } |
|
|
|
val tabs = listOf("Settings", "Audio", "Storage") |
|
|
|
val tabs = listOf("Settings", "Audio", "Storage", "Radio") |
|
|
|
|
|
|
|
|
|
|
|
val handleBack = { |
|
|
|
val handleBack = { |
|
|
|
if (dirty && NativeLib.isReady()) { |
|
|
|
if (dirty && NativeLib.isReady()) { |
|
|
|
@ -88,6 +90,7 @@ fun SettingsScreen(vm: ChatViewModel, onBack: () -> Unit, onExit: () -> Unit = { |
|
|
|
0 -> GeneralSettingsTab(vm, dirty, onDirty = { dirty = it }, onExit) |
|
|
|
0 -> GeneralSettingsTab(vm, dirty, onDirty = { dirty = it }, onExit) |
|
|
|
1 -> AudioSettingsTab(vm) |
|
|
|
1 -> AudioSettingsTab(vm) |
|
|
|
2 -> StorageSettingsTab() |
|
|
|
2 -> StorageSettingsTab() |
|
|
|
|
|
|
|
3 -> RadioSettingsTab() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -359,49 +362,6 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool |
|
|
|
|
|
|
|
|
|
|
|
HorizontalDivider(Modifier.padding(vertical = 8.dp)) |
|
|
|
HorizontalDivider(Modifier.padding(vertical = 8.dp)) |
|
|
|
|
|
|
|
|
|
|
|
/* ── Рация (PTT) ── */ |
|
|
|
|
|
|
|
Text("Рация (PTT)", style = MaterialTheme.typography.titleMedium) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var volumeUpPtt by remember { mutableStateOf(provider.getRadioVolumeUpPtt()) } |
|
|
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) { |
|
|
|
|
|
|
|
Text("Volume-up (удержание) — передача", modifier = Modifier.weight(1f)) |
|
|
|
|
|
|
|
Switch(checked = volumeUpPtt, onCheckedChange = { |
|
|
|
|
|
|
|
volumeUpPtt = it |
|
|
|
|
|
|
|
scope.launch { provider.setRadioVolumeUpPtt(it) } |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var overlayEnabled by remember { mutableStateOf(provider.getRadioOverlay()) } |
|
|
|
|
|
|
|
val canOverlay = RadioOverlayService.canDrawOverlays(ChatApplication.instance) |
|
|
|
|
|
|
|
Row( |
|
|
|
|
|
|
|
modifier = Modifier.fillMaxWidth().clickable(enabled = !canOverlay) { |
|
|
|
|
|
|
|
if (!canOverlay) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
ChatApplication.instance.startActivity( |
|
|
|
|
|
|
|
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, |
|
|
|
|
|
|
|
Uri.parse("package:${ChatApplication.instance.packageName}"))) |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
|
|
LogManager.addLog("ERROR", "SETTINGS", "open overlay permission failed: ${e.message}") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
verticalAlignment = Alignment.CenterVertically |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
Column(Modifier.weight(1f)) { |
|
|
|
|
|
|
|
Text("PTT-кнопка поверх приложений") |
|
|
|
|
|
|
|
if (!canOverlay) Text("Требуется разрешение «поверх других приложений» — нажмите", |
|
|
|
|
|
|
|
style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Switch(checked = overlayEnabled && canOverlay, enabled = canOverlay, onCheckedChange = { |
|
|
|
|
|
|
|
overlayEnabled = it |
|
|
|
|
|
|
|
scope.launch { provider.setRadioOverlay(it) } |
|
|
|
|
|
|
|
if (it) RadioOverlayService.start(ChatApplication.instance) |
|
|
|
|
|
|
|
else RadioOverlayService.stop(ChatApplication.instance) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HorizontalDivider(Modifier.padding(vertical = 8.dp)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Debug */ |
|
|
|
/* Debug */ |
|
|
|
Text("Debug", style = MaterialTheme.typography.titleMedium) |
|
|
|
Text("Debug", style = MaterialTheme.typography.titleMedium) |
|
|
|
DebugLevelDropdown(label = "Global Level", current = debugLevel.uppercase(), onSelect = setDebug) |
|
|
|
DebugLevelDropdown(label = "Global Level", current = debugLevel.uppercase(), onSelect = setDebug) |
|
|
|
@ -502,6 +462,68 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool |
|
|
|
dismissButton = { TextButton(onClick = { showClearDb = false }) { Text("Cancel") } }) |
|
|
|
dismissButton = { TextButton(onClick = { showClearDb = false }) { Text("Cancel") } }) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ── Radio Settings Tab ── */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Composable |
|
|
|
|
|
|
|
private fun RadioSettingsTab() { |
|
|
|
|
|
|
|
val provider = ChatApplication.instance.configProvider |
|
|
|
|
|
|
|
val scope = rememberCoroutineScope() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Column( |
|
|
|
|
|
|
|
modifier = Modifier.padding(16.dp).verticalScroll(rememberScrollState()), |
|
|
|
|
|
|
|
verticalArrangement = Arrangement.spacedBy(12.dp) |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
/* ── Volume-кнопки PTT ── */ |
|
|
|
|
|
|
|
var volumePtt by remember { mutableStateOf(provider.getRadioVolumePtt()) } |
|
|
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) { |
|
|
|
|
|
|
|
Text("Кнопки громкости (удержание) — передача", modifier = Modifier.weight(1f)) |
|
|
|
|
|
|
|
Switch(checked = volumePtt, onCheckedChange = { |
|
|
|
|
|
|
|
volumePtt = it |
|
|
|
|
|
|
|
scope.launch { provider.setRadioVolumePtt(it) } |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HorizontalDivider(Modifier.padding(vertical = 4.dp)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ── Оверлей PTT поверх приложений ── */ |
|
|
|
|
|
|
|
var overlayEnabled by remember { mutableStateOf(provider.getRadioOverlay()) } |
|
|
|
|
|
|
|
var canOverlay by remember { mutableStateOf(RadioOverlayService.canDrawOverlays(ChatApplication.instance)) } |
|
|
|
|
|
|
|
LifecycleResumeEffect(Unit) { |
|
|
|
|
|
|
|
canOverlay = RadioOverlayService.canDrawOverlays(ChatApplication.instance) |
|
|
|
|
|
|
|
onPauseOrDispose { } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
val openOverlayPermission = { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
val i = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, |
|
|
|
|
|
|
|
Uri.parse("package:${ChatApplication.instance.packageName}")) |
|
|
|
|
|
|
|
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
|
|
|
|
|
|
|
ChatApplication.instance.startActivity(i) |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
|
|
LogManager.addLog("ERROR", "SETTINGS", "open overlay permission failed: ${e.message}") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) { |
|
|
|
|
|
|
|
Column(Modifier.weight(1f)) { |
|
|
|
|
|
|
|
Text("PTT-кнопка поверх приложений") |
|
|
|
|
|
|
|
if (!canOverlay) Text( |
|
|
|
|
|
|
|
"Требуется разрешение «поверх других приложений» — откройте настройки", |
|
|
|
|
|
|
|
style = MaterialTheme.typography.bodySmall, |
|
|
|
|
|
|
|
color = MaterialTheme.colorScheme.primary, |
|
|
|
|
|
|
|
textDecoration = TextDecoration.Underline, |
|
|
|
|
|
|
|
modifier = Modifier.clickable { openOverlayPermission() }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Switch(checked = overlayEnabled && canOverlay, enabled = canOverlay, onCheckedChange = { |
|
|
|
|
|
|
|
overlayEnabled = it |
|
|
|
|
|
|
|
scope.launch { provider.setRadioOverlay(it) } |
|
|
|
|
|
|
|
if (it && RadioAudioEngine.instance().isActive()) |
|
|
|
|
|
|
|
RadioOverlayService.start(ChatApplication.instance) |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
RadioOverlayService.stop(ChatApplication.instance) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* ── Audio Settings Tab ── */ |
|
|
|
/* ── Audio Settings Tab ── */ |
|
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class) |
|
|
|
@OptIn(ExperimentalMaterial3Api::class) |
|
|
|
@ -582,8 +604,10 @@ private fun AudioSettingsTab(vm: ChatViewModel) { |
|
|
|
) { |
|
|
|
) { |
|
|
|
val ctx = ChatApplication.instance |
|
|
|
val ctx = ChatApplication.instance |
|
|
|
try { |
|
|
|
try { |
|
|
|
ctx.startActivity(Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT, |
|
|
|
val i = Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT, |
|
|
|
Uri.parse("package:${ctx.packageName}"))) |
|
|
|
Uri.parse("package:${ctx.packageName}")) |
|
|
|
|
|
|
|
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
|
|
|
|
|
|
|
ctx.startActivity(i) |
|
|
|
} catch (e: Exception) { |
|
|
|
} catch (e: Exception) { |
|
|
|
LogManager.addLog("ERROR", "SETTINGS", "open full-screen-intent settings failed: ${e.message}") |
|
|
|
LogManager.addLog("ERROR", "SETTINGS", "open full-screen-intent settings failed: ${e.message}") |
|
|
|
} |
|
|
|
} |
|
|
|
|