669 lines
22 KiB
Kotlin
669 lines
22 KiB
Kotlin
package com.bodycamera.twentyfoulabs
|
|
|
|
import android.Manifest
|
|
import android.content.BroadcastReceiver
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.IntentFilter
|
|
import android.content.pm.PackageManager
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.util.Log
|
|
import android.view.KeyEvent
|
|
import android.view.View
|
|
import android.view.WindowManager
|
|
import android.widget.Toast
|
|
import androidx.activity.ComponentActivity
|
|
import androidx.activity.OnBackPressedCallback
|
|
import androidx.activity.compose.setContent
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
import androidx.activity.viewModels
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.navigation.compose.NavHost
|
|
import androidx.navigation.compose.composable
|
|
import androidx.navigation.compose.rememberNavController
|
|
import com.bodycamera.twentyfoulabs.data.device.M530DeviceController
|
|
import com.bodycamera.twentyfoulabs.data.auth.AuthSessionStore
|
|
import com.bodycamera.twentyfoulabs.data.ptt.PttManager
|
|
import com.bodycamera.twentyfoulabs.navigation.LocalNavigationHandler
|
|
import com.bodycamera.twentyfoulabs.navigation.rememberNavigationHandler
|
|
import com.bodycamera.twentyfoulabs.ui.screens.ConnectionSettingsScreen
|
|
import com.bodycamera.twentyfoulabs.ui.screens.ConnectionSettingsViewModel
|
|
import com.bodycamera.twentyfoulabs.ui.screens.MainScreen
|
|
import com.bodycamera.twentyfoulabs.ui.screens.MainViewModel
|
|
import com.bodycamera.twentyfoulabs.ui.screens.AuthViewModel
|
|
import com.bodycamera.twentyfoulabs.ui.screens.AuthUiState
|
|
import com.bodycamera.twentyfoulabs.ui.screens.LoginScreen
|
|
import com.bodycamera.twentyfoulabs.ui.screens.VideoConfigScreen
|
|
import com.bodycamera.twentyfoulabs.ui.screens.VideoConfigViewModel
|
|
import com.bodycamera.twentyfoulabs.ui.theme.MainAppTheme
|
|
import dagger.hilt.android.AndroidEntryPoint
|
|
import androidx.hilt.navigation.compose.hiltViewModel
|
|
import javax.inject.Inject
|
|
import androidx.compose.runtime.collectAsState
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.animation.core.Animatable
|
|
import androidx.compose.animation.core.tween
|
|
import androidx.compose.foundation.background
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Spacer
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.height
|
|
import androidx.compose.foundation.layout.width
|
|
import androidx.compose.foundation.text.BasicText
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.draw.alpha
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
import androidx.compose.ui.text.TextStyle
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.sp
|
|
import kotlinx.coroutines.delay
|
|
import androidx.lifecycle.lifecycleScope
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.flow.collect
|
|
|
|
@Composable
|
|
private fun M530StartupSplash() {
|
|
|
|
val alpha = remember {
|
|
Animatable(0f)
|
|
}
|
|
|
|
val scale = remember {
|
|
Animatable(0.90f)
|
|
}
|
|
|
|
LaunchedEffect(Unit) {
|
|
|
|
launch {
|
|
alpha.animateTo(
|
|
targetValue = 1f,
|
|
animationSpec = tween(650)
|
|
)
|
|
}
|
|
|
|
launch {
|
|
scale.animateTo(
|
|
targetValue = 1f,
|
|
animationSpec = tween(750)
|
|
)
|
|
}
|
|
}
|
|
|
|
Box(
|
|
modifier = Modifier
|
|
.fillMaxSize()
|
|
.background(Color(0xFF071827)),
|
|
contentAlignment = Alignment.Center
|
|
) {
|
|
|
|
Column(
|
|
modifier = Modifier
|
|
.alpha(alpha.value)
|
|
.graphicsLayer {
|
|
scaleX = scale.value
|
|
scaleY = scale.value
|
|
},
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
verticalArrangement = Arrangement.Center
|
|
) {
|
|
|
|
BasicText(
|
|
text = "GEOSATELITAL",
|
|
style = TextStyle(
|
|
color = Color.White,
|
|
fontSize = 28.sp,
|
|
fontWeight = FontWeight.Bold
|
|
)
|
|
)
|
|
|
|
Spacer(
|
|
modifier = Modifier.height(8.dp)
|
|
)
|
|
|
|
BasicText(
|
|
text = "M530 COMMAND",
|
|
style = TextStyle(
|
|
color = Color(0xFF00D7FF),
|
|
fontSize = 16.sp,
|
|
fontWeight = FontWeight.Medium
|
|
)
|
|
)
|
|
|
|
Spacer(
|
|
modifier = Modifier.height(28.dp)
|
|
)
|
|
|
|
Box(
|
|
modifier = Modifier
|
|
.width(100.dp)
|
|
.height(3.dp)
|
|
.background(Color(0xFF00D7FF))
|
|
)
|
|
|
|
Spacer(
|
|
modifier = Modifier.height(12.dp)
|
|
)
|
|
|
|
BasicText(
|
|
text = "INICIANDO...",
|
|
style = TextStyle(
|
|
color = Color(0xFFB7C6D3),
|
|
fontSize = 11.sp
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@AndroidEntryPoint
|
|
class MainActivity : ComponentActivity() {
|
|
|
|
private val viewModel: MainViewModel by viewModels()
|
|
|
|
@Inject lateinit var deviceController: M530DeviceController
|
|
@Inject lateinit var authSessionStore: AuthSessionStore
|
|
@Inject lateinit var pttManager: PttManager
|
|
|
|
@Volatile private var isAuthenticated = false
|
|
private var frontPreviewOpened = false
|
|
|
|
companion object {
|
|
private const val TAG = "MainActivity"
|
|
private const val ACTION_VIDEO_DOWN = "android.intent.action.ACTION_VIDEO_DOWN"
|
|
private const val ACTION_VIDEO_UP = "android.intent.action.ACTION_VIDEO_UP"
|
|
|
|
private const val ACTION_PTT_DOWN =
|
|
"android.intent.action.ACTION_PTTKEY_DOWN"
|
|
|
|
private const val ACTION_PTT_UP =
|
|
"android.intent.action.ACTION_PTTKEY_UP"
|
|
|
|
private const val ACTION_PTT_LONG_PRESS =
|
|
"android.intent.action.ACTION_PTTKEY_LONG_PRESS"
|
|
}
|
|
|
|
private var isReceiverRegistered = false
|
|
|
|
private val hardwareReceiver = object : BroadcastReceiver() {
|
|
override fun onReceive(context: Context?, intent: Intent?) {
|
|
when (intent?.action) {
|
|
|
|
ACTION_VIDEO_DOWN -> handleVideoButton()
|
|
|
|
ACTION_VIDEO_UP -> Unit
|
|
|
|
ACTION_PTT_DOWN -> {
|
|
Log.i(TAG, "PTT BROADCAST DOWN recibido")
|
|
|
|
if (!isAuthenticated) {
|
|
toast("Inicia sesion para usar PTT")
|
|
return
|
|
}
|
|
|
|
val accepted = pttManager.pressToTalk()
|
|
Log.i(TAG, if (accepted) "PTT LIVE DOWN - solicitud enviada" else "PTT LIVE DOWN - PTT no preparado")
|
|
}
|
|
|
|
ACTION_PTT_LONG_PRESS -> {
|
|
Log.d(TAG, "PTT BROADCAST LONG PRESS ignorado")
|
|
}
|
|
|
|
ACTION_PTT_UP -> {
|
|
Log.i(TAG, "PTT BROADCAST UP recibido")
|
|
|
|
if (!isAuthenticated) {
|
|
return
|
|
}
|
|
|
|
pttManager.releaseTalk()
|
|
Log.i(TAG, "PTT LIVE UP - transmision liberada")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private val permissionLauncher = registerForActivityResult(
|
|
ActivityResultContracts.RequestMultiplePermissions()
|
|
) { permissions ->
|
|
val denied = permissions.filterValues { !it }.keys
|
|
if (denied.isNotEmpty()) {
|
|
Toast.makeText(this, "Permisos pendientes: ${denied.joinToString()}", Toast.LENGTH_LONG).show()
|
|
}
|
|
}
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
Log.i(TAG, "M530 Controller onCreate")
|
|
|
|
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
|
window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
requestPermissions()
|
|
|
|
pttManager.initialize(
|
|
object : PttManager.Listener {
|
|
override fun onPttReady() = Unit
|
|
override fun onPttDisconnected() = Unit
|
|
override fun onTransmitChanged(transmitting: Boolean) = Unit
|
|
override fun onChannelBusy(holderName: String?) = Unit
|
|
override fun onError(message: String) = Unit
|
|
}
|
|
)
|
|
|
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
|
override fun handleOnBackPressed() = Unit
|
|
})
|
|
|
|
lifecycleScope.launch {
|
|
authSessionStore.token.collect { token ->
|
|
isAuthenticated = !token.isNullOrBlank()
|
|
|
|
if (!token.isNullOrBlank()) {
|
|
pttManager.start(token)
|
|
} else {
|
|
pttManager.stop()
|
|
}
|
|
|
|
if (false && isAuthenticated && !frontPreviewOpened) {
|
|
frontPreviewOpened = true
|
|
runOnUiThread {
|
|
openFrontPreview()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
registerHardwareReceiver()
|
|
deviceController.logTelemetry()
|
|
|
|
setContent {
|
|
MainAppTheme {
|
|
|
|
var showStartupSplash by remember {
|
|
mutableStateOf(true)
|
|
}
|
|
|
|
LaunchedEffect(Unit) {
|
|
delay(2000)
|
|
showStartupSplash = false
|
|
}
|
|
|
|
if (showStartupSplash) {
|
|
|
|
M530StartupSplash()
|
|
|
|
} else {
|
|
|
|
AppNavigation(
|
|
viewModel = viewModel,
|
|
onOpenPhotoCamera = { handlePhotoButton() },
|
|
onOpenVideoCamera = { handleVideoButton() }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
|
if (event?.repeatCount ?: 0 > 0) return true
|
|
Log.i(TAG, "M530 KEY DOWN: $keyCode")
|
|
if (!isAuthenticated && keyCode in setOf(KeyEvent.KEYCODE_F1, KeyEvent.KEYCODE_F2, KeyEvent.KEYCODE_F3, KeyEvent.KEYCODE_F4, KeyEvent.KEYCODE_F5, KeyEvent.KEYCODE_F7)) {
|
|
toast("Inicia sesion para usar la bodycam")
|
|
return true
|
|
}
|
|
return when (keyCode) {
|
|
KeyEvent.KEYCODE_F1 -> {
|
|
|
|
toast("Marcando evidencia como favorita...")
|
|
|
|
deviceController.markEvidence(
|
|
"F1_STAR"
|
|
) { result ->
|
|
|
|
runOnUiThread {
|
|
|
|
if (result.isSuccess) {
|
|
|
|
val mediaId =
|
|
result.getOrNull()
|
|
|
|
toast(
|
|
"Evidencia favorita registrada (ID $mediaId)"
|
|
)
|
|
|
|
} else {
|
|
|
|
toast(
|
|
result.exceptionOrNull()?.message
|
|
?: "No se pudo marcar favorito"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
true
|
|
}
|
|
KeyEvent.KEYCODE_F2 -> {
|
|
handlePhotoButton()
|
|
true
|
|
}
|
|
KeyEvent.KEYCODE_F3 -> {
|
|
if (deviceController.isMicRecording()) {
|
|
val result = deviceController.stopMicRecording()
|
|
toast(
|
|
if (result.isSuccess)
|
|
"MIC: grabacion guardada"
|
|
else
|
|
"MIC error: ${result.exceptionOrNull()?.message}"
|
|
)
|
|
} else {
|
|
val result = deviceController.startMicRecording()
|
|
toast(
|
|
if (result.isSuccess)
|
|
"MIC: grabando"
|
|
else
|
|
"MIC error: ${result.exceptionOrNull()?.message}"
|
|
)
|
|
}
|
|
true
|
|
}
|
|
KeyEvent.KEYCODE_F4 -> {
|
|
true
|
|
}
|
|
KeyEvent.KEYCODE_F5 -> {
|
|
// Algunas versiones del firmware tambien emiten ACTION_VIDEO_DOWN.
|
|
// El ViewModel aplica debounce para evitar doble ejecucion.
|
|
handleVideoButton()
|
|
true
|
|
}
|
|
KeyEvent.KEYCODE_F7 -> {
|
|
handleSosButton()
|
|
true
|
|
}
|
|
else -> super.onKeyDown(keyCode, event)
|
|
}
|
|
}
|
|
|
|
override fun onKeyUp(
|
|
keyCode: Int,
|
|
event: KeyEvent?
|
|
): Boolean {
|
|
|
|
Log.i(
|
|
TAG,
|
|
"M530 KEY UP: $keyCode"
|
|
)
|
|
|
|
if (
|
|
!isAuthenticated &&
|
|
keyCode in setOf(
|
|
KeyEvent.KEYCODE_F1,
|
|
KeyEvent.KEYCODE_F2,
|
|
KeyEvent.KEYCODE_F3,
|
|
KeyEvent.KEYCODE_F4,
|
|
KeyEvent.KEYCODE_F5,
|
|
KeyEvent.KEYCODE_F7
|
|
)
|
|
) {
|
|
return true
|
|
}
|
|
|
|
return when (keyCode) {
|
|
|
|
KeyEvent.KEYCODE_F4 -> {
|
|
true
|
|
}
|
|
|
|
KeyEvent.KEYCODE_F1,
|
|
KeyEvent.KEYCODE_F2,
|
|
KeyEvent.KEYCODE_F3,
|
|
KeyEvent.KEYCODE_F5,
|
|
KeyEvent.KEYCODE_F7 -> true
|
|
|
|
else ->
|
|
super.onKeyUp(
|
|
keyCode,
|
|
event
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun handleVideoButton() {
|
|
if (!isAuthenticated) {
|
|
toast("Inicia sesion para usar VIDEO")
|
|
return
|
|
}
|
|
startActivity(
|
|
android.content.Intent(
|
|
this,
|
|
com.bodycamera.twentyfoulabs.camera.M530CameraActivity::class.java
|
|
).putExtra(
|
|
com.bodycamera.twentyfoulabs.camera.M530CameraActivity.EXTRA_MODE,
|
|
com.bodycamera.twentyfoulabs.camera.M530CameraActivity.MODE_VIDEO
|
|
).addFlags(android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
)
|
|
}
|
|
|
|
|
|
|
|
private fun openFrontPreview() {
|
|
if (!isAuthenticated) return
|
|
|
|
startActivity(
|
|
android.content.Intent(
|
|
this,
|
|
com.bodycamera.twentyfoulabs.camera.M530CameraActivity::class.java
|
|
).putExtra(
|
|
com.bodycamera.twentyfoulabs.camera.M530CameraActivity.EXTRA_MODE,
|
|
com.bodycamera.twentyfoulabs.camera.M530CameraActivity.MODE_FRONT_PREVIEW
|
|
).addFlags(android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
)
|
|
}
|
|
private fun handlePhotoButton() {
|
|
startActivity(
|
|
android.content.Intent(
|
|
this,
|
|
com.bodycamera.twentyfoulabs.camera.M530CameraActivity::class.java
|
|
).addFlags(android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
)
|
|
}
|
|
private fun handleSosButton() {
|
|
// F7 alterna SOS/DETENER. La misma orden de parada que usa la pantalla.
|
|
if (viewModel.isSosSessionActive()) {
|
|
Log.i(TAG, "F7 DETENER SOS: solicitando cierre de WebRTC")
|
|
viewModel.stopVideoFromUi()
|
|
toast("Deteniendo video SOS...")
|
|
return
|
|
}
|
|
|
|
Log.i(TAG, "F7 INICIAR SOS")
|
|
toast("Enviando SOS...")
|
|
|
|
viewModel.triggerSosFromUi { result ->
|
|
|
|
runOnUiThread {
|
|
|
|
if (result.isSuccess) {
|
|
|
|
val alertId = result.getOrNull()
|
|
|
|
Log.w(
|
|
TAG,
|
|
"SOS ENVIADO id=$alertId"
|
|
)
|
|
|
|
toast("SOS ENVIADO")
|
|
|
|
} else {
|
|
|
|
val message =
|
|
result.exceptionOrNull()?.message
|
|
?: "No se pudo enviar SOS"
|
|
|
|
Log.e(
|
|
TAG,
|
|
"SOS NO ENVIADO: $message"
|
|
)
|
|
|
|
toast(message)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun registerHardwareReceiver() {
|
|
if (isReceiverRegistered) return
|
|
val filter = IntentFilter().apply {
|
|
addAction(ACTION_VIDEO_DOWN)
|
|
addAction(ACTION_VIDEO_UP)
|
|
addAction(ACTION_PTT_DOWN)
|
|
addAction(ACTION_PTT_UP)
|
|
addAction(ACTION_PTT_LONG_PRESS)
|
|
}
|
|
try {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
registerReceiver(hardwareReceiver, filter, RECEIVER_EXPORTED)
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
registerReceiver(hardwareReceiver, filter)
|
|
}
|
|
isReceiverRegistered = true
|
|
} catch (t: Throwable) {
|
|
Log.e(TAG, "No se pudo registrar receiver", t)
|
|
}
|
|
}
|
|
|
|
private fun unregisterHardwareReceiver() {
|
|
if (!isReceiverRegistered) return
|
|
try { unregisterReceiver(hardwareReceiver) } catch (_: Throwable) {}
|
|
isReceiverRegistered = false
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
hideSystemUI()
|
|
registerHardwareReceiver()
|
|
}
|
|
|
|
override fun onPause() {
|
|
super.onPause()
|
|
|
|
// El receiver permanece activo para capturar
|
|
// botones fisicos mientras otra Activity de la app
|
|
// (por ejemplo la camara) esta en primer plano.
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
unregisterHardwareReceiver()
|
|
deviceController.release()
|
|
super.onDestroy()
|
|
}
|
|
|
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
|
super.onWindowFocusChanged(hasFocus)
|
|
if (hasFocus) hideSystemUI()
|
|
}
|
|
|
|
private fun hideSystemUI() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
window.setDecorFitsSystemWindows(false)
|
|
window.insetsController?.let { controller ->
|
|
controller.hide(android.view.WindowInsets.Type.statusBars())
|
|
controller.hide(android.view.WindowInsets.Type.navigationBars())
|
|
controller.systemBarsBehavior = android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
}
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or
|
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
}
|
|
}
|
|
|
|
private fun requestPermissions() {
|
|
val requested = listOf(
|
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
Manifest.permission.ACCESS_COARSE_LOCATION,
|
|
Manifest.permission.CAMERA,
|
|
Manifest.permission.RECORD_AUDIO
|
|
).filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }
|
|
if (requested.isNotEmpty()) permissionLauncher.launch(requested.toTypedArray())
|
|
}
|
|
|
|
private fun toast(message: String) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
|
}
|
|
|
|
@Composable
|
|
fun AppNavigation(viewModel: MainViewModel, onOpenPhotoCamera: () -> Unit, onOpenVideoCamera: () -> Unit) {
|
|
val navController = rememberNavController()
|
|
val navigationHandler = rememberNavigationHandler(navController)
|
|
val authViewModel: AuthViewModel = hiltViewModel()
|
|
val authState by authViewModel.state.collectAsState()
|
|
|
|
LaunchedEffect(authState) {
|
|
if (authState is AuthUiState.Authenticated && navController.currentDestination?.route != "main") {
|
|
navController.navigate("main") { popUpTo("login") { inclusive = true } }
|
|
}
|
|
}
|
|
|
|
CompositionLocalProvider(LocalNavigationHandler provides navigationHandler) {
|
|
NavHost(
|
|
navController = navController,
|
|
startDestination = "login"
|
|
) {
|
|
composable("login") {
|
|
LoginScreen(
|
|
state = authState,
|
|
onLogin = authViewModel::login,
|
|
onQrLogin = authViewModel::qrLogin,
|
|
onRetrySession = authViewModel::restoreSession,
|
|
onClearError = authViewModel::clearError
|
|
)
|
|
}
|
|
|
|
composable("main") {
|
|
MainScreen(
|
|
viewModel = viewModel,
|
|
onNavigateToSettings = { navController.navigate("connection_settings") },
|
|
onNavigateToVideoConfig = { navController.navigate("video_config") },
|
|
onOpenPhotoCamera = onOpenPhotoCamera,
|
|
onOpenVideoCamera = onOpenVideoCamera,
|
|
onLogout = {
|
|
// Liberar WebRTC antes de mostrar nuevamente el lector del fotocheck.
|
|
viewModel.stopVideoFromUi()
|
|
authViewModel.logout()
|
|
navController.navigate("login") { popUpTo("main") { inclusive = true } }
|
|
}
|
|
)
|
|
}
|
|
|
|
composable("connection_settings") {
|
|
val settingsViewModel: ConnectionSettingsViewModel = hiltViewModel()
|
|
ConnectionSettingsScreen(viewModel = settingsViewModel)
|
|
}
|
|
|
|
composable("video_config") {
|
|
val videoConfigViewModel: VideoConfigViewModel = hiltViewModel()
|
|
VideoConfigScreen(viewModel = videoConfigViewModel)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|