global fix
This commit is contained in:
35
.idea/copilotDiffState.xml
generated
35
.idea/copilotDiffState.xml
generated
File diff suppressed because one or more lines are too long
@@ -19,6 +19,7 @@
|
|||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ class CameraManager(private val context: Context) {
|
|||||||
|
|
||||||
cameraProviderFuture.addListener(
|
cameraProviderFuture.addListener(
|
||||||
{
|
{
|
||||||
|
Log.d("CameraManager", "cameraProviderFuture listener invoked")
|
||||||
try {
|
try {
|
||||||
cameraProvider = cameraProviderFuture.get()
|
cameraProvider = cameraProviderFuture.get()
|
||||||
|
Log.d("CameraManager", "cameraProvider obtained: $cameraProvider")
|
||||||
|
|
||||||
// Create preview
|
// Create preview
|
||||||
val preview = Preview.Builder()
|
val preview = Preview.Builder()
|
||||||
@@ -45,6 +47,7 @@ class CameraManager(private val context: Context) {
|
|||||||
|
|
||||||
// Select back camera
|
// Select back camera
|
||||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
Log.d("CameraManager", "Using camera selector: $cameraSelector")
|
||||||
|
|
||||||
// Unbind all use cases
|
// Unbind all use cases
|
||||||
cameraProvider?.unbindAll()
|
cameraProvider?.unbindAll()
|
||||||
@@ -57,6 +60,7 @@ class CameraManager(private val context: Context) {
|
|||||||
imageCapture
|
imageCapture
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Log.d("CameraManager", "bindToLifecycle called")
|
||||||
Log.d("CameraManager", "Camera started successfully")
|
Log.d("CameraManager", "Camera started successfully")
|
||||||
} catch (exc: Exception) {
|
} catch (exc: Exception) {
|
||||||
Log.e("CameraManager", "Use case binding failed", exc)
|
Log.e("CameraManager", "Use case binding failed", exc)
|
||||||
@@ -105,4 +109,3 @@ class CameraManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.os.Bundle
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.camera.view.PreviewView
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -20,7 +19,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Call
|
|
||||||
import androidx.compose.material.icons.filled.CallEnd
|
import androidx.compose.material.icons.filled.CallEnd
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
@@ -31,19 +29,22 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.ui.Alignment
|
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.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@@ -85,13 +86,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StreamingApp(modifier: Modifier = Modifier) {
|
fun StreamingApp(modifier: Modifier = Modifier) {
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
PermissionChecker(
|
PermissionChecker(
|
||||||
onPermissionsGranted = {
|
onPermissionsGranted = { /* permissions granted callback - do not call composables here */ }
|
||||||
// Разрешения выданы, показываем основное приложение
|
|
||||||
StreamingAppContent(modifier)
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
// Если разрешения не выданы, PermissionChecker сам покажет запрос
|
// Если разрешения не выданы, PermissionChecker сам покажет запрос
|
||||||
StreamingAppContent(modifier)
|
StreamingAppContent(modifier)
|
||||||
@@ -112,8 +108,6 @@ fun StreamingAppContent(modifier: Modifier = Modifier) {
|
|||||||
var password by remember { mutableStateOf("") }
|
var password by remember { mutableStateOf("") }
|
||||||
var showConnectionForm by remember { mutableStateOf(true) }
|
var showConnectionForm by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
@@ -276,24 +270,19 @@ fun StreamingScreen(
|
|||||||
onDisconnect: () -> Unit
|
onDisconnect: () -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
val cameraManager = remember { CameraManager(context) }
|
val cameraManager = remember { CameraManager(context) }
|
||||||
|
|
||||||
// Проверяем разрешения на камеру
|
// Проверяем разрешения на камеру
|
||||||
val hasCameraPermission = PermissionManager.hasCameraPermission(context)
|
val hasCameraPermission = PermissionManager.hasCameraPermission(context)
|
||||||
val hasInternetPermission = PermissionManager.hasInternetPermission(context)
|
val hasInternetPermission = PermissionManager.hasInternetPermission(context)
|
||||||
|
|
||||||
LaunchedEffect(isCameraRunning) {
|
|
||||||
if (isCameraRunning && hasCameraPermission) {
|
|
||||||
// Start camera preview when connected
|
|
||||||
// In a real app, would bind to lifecycle and PreviewView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
verticalArrangement = Arrangement.SpaceBetween
|
verticalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
// Camera preview placeholder
|
// Camera preview area using PreviewView
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -340,13 +329,36 @@ fun StreamingScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (isCameraRunning) {
|
} else if (isCameraRunning) {
|
||||||
// Camera preview would be rendered here
|
// Render PreviewView via AndroidView and start camera
|
||||||
// Using AndroidView with PreviewView in a real implementation
|
var previewViewRef: PreviewView? by remember { mutableStateOf(null) }
|
||||||
Text(
|
|
||||||
text = "🎥 Camera Preview",
|
AndroidView(
|
||||||
color = Color.White,
|
factory = { ctx ->
|
||||||
style = MaterialTheme.typography.headlineSmall
|
PreviewView(ctx).apply {
|
||||||
|
scaleType = PreviewView.ScaleType.FILL_CENTER
|
||||||
|
// Keep reference for starting camera
|
||||||
|
previewViewRef = this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Start/stop camera based on lifecycle and preview availability
|
||||||
|
DisposableEffect(previewViewRef, isCameraRunning) {
|
||||||
|
val pv = previewViewRef
|
||||||
|
if (pv != null && isCameraRunning) {
|
||||||
|
cameraManager.startCamera(
|
||||||
|
lifecycleOwner,
|
||||||
|
pv.surfaceProvider,
|
||||||
|
onError = { err -> Log.e("CameraManager", "Camera error: $err") }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
cameraManager.stopCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "Camera Inactive",
|
text = "Camera Inactive",
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ data class ServerConnectionConfig(
|
|||||||
val password: String
|
val password: String
|
||||||
) {
|
) {
|
||||||
fun getWebSocketUrl(): String {
|
fun getWebSocketUrl(): String {
|
||||||
return "ws://$serverHost:$serverPort/ws/client/$roomId/$password"
|
// URL-encode roomId and password to handle special characters and avoid accidental mismatches
|
||||||
|
val encRoom = try { java.net.URLEncoder.encode(roomId, "UTF-8") } catch (e: Exception) { roomId }
|
||||||
|
val encPass = try { java.net.URLEncoder.encode(password, "UTF-8") } catch (e: Exception) { password }
|
||||||
|
return "ws://$serverHost:$serverPort/ws/client/$encRoom/$encPass"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,4 +44,3 @@ object VideoCommands {
|
|||||||
fun adjustQuality(quality: Int) = VideoCommand(type = "adjust_quality", quality = quality)
|
fun adjustQuality(quality: Int) = VideoCommand(type = "adjust_quality", quality = quality)
|
||||||
fun reset() = VideoCommand(type = "reset")
|
fun reset() = VideoCommand(type = "reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,8 +98,34 @@ class StreamViewModel : ViewModel() {
|
|||||||
private fun onMessage(message: String) {
|
private fun onMessage(message: String) {
|
||||||
Log.d("StreamViewModel", "Message received: $message")
|
Log.d("StreamViewModel", "Message received: $message")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!message.contains("ping")) {
|
try {
|
||||||
updateStatus("Получено: $message")
|
// Try to parse as ConnectionResponse
|
||||||
|
val gson = com.google.gson.Gson()
|
||||||
|
val connectionResponse = try {
|
||||||
|
gson.fromJson(message, ConnectionResponse::class.java)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectionResponse != null && (connectionResponse.client_id != null || connectionResponse.success)) {
|
||||||
|
Log.d("StreamViewModel", "ConnectionResponse: success=${connectionResponse.success}, client_id=${connectionResponse.client_id}, room_id=${connectionResponse.room_id}, error=${connectionResponse.error}")
|
||||||
|
if (connectionResponse.success) {
|
||||||
|
updateStatus("Сессия создана. Client ID: ${connectionResponse.client_id ?: "-"}")
|
||||||
|
} else {
|
||||||
|
updateStatus("Ошибка на сервере: ${connectionResponse.error ?: "unknown"}")
|
||||||
|
_connectionState.value = ConnectionState.Error(connectionResponse.error ?: "Server error")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// General message handling
|
||||||
|
if (!message.contains("ping")) {
|
||||||
|
updateStatus("Получено: $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("StreamViewModel", "Failed to handle message: ${e.message}")
|
||||||
|
if (!message.contains("ping")) {
|
||||||
|
updateStatus("Получено: $message")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,4 +199,3 @@ sealed class ConnectionState {
|
|||||||
object Disconnected : ConnectionState()
|
object Disconnected : ConnectionState()
|
||||||
data class Error(val message: String) : ConnectionState()
|
data class Error(val message: String) : ConnectionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,16 @@ class WebSocketManager(
|
|||||||
fun isConnected(): Boolean = webSocket != null
|
fun isConnected(): Boolean = webSocket != null
|
||||||
|
|
||||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||||
Log.d("WebSocket", "Connected!")
|
Log.d("WebSocket", "Connected! Response code=${response.code}, message=${response.message}")
|
||||||
|
try {
|
||||||
|
val headers = response.headers
|
||||||
|
for (i in 0 until headers.size) {
|
||||||
|
Log.d("WebSocket", "Header: ${headers.name(i)}=${headers.value(i)}")
|
||||||
|
}
|
||||||
|
Log.d("WebSocket", "Request url: ${response.request.url}")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("WebSocket", "Failed to log headers: ${e.message}")
|
||||||
|
}
|
||||||
onConnected()
|
onConnected()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,4 +114,3 @@ class WebSocketManager(
|
|||||||
onDisconnected()
|
onDisconnected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
app/src/main/res/xml/network_security_config.xml
Normal file
11
app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<!-- Allow cleartext for local development host and base-config for debug devices -->
|
||||||
|
<base-config cleartextTrafficPermitted="true" />
|
||||||
|
|
||||||
|
<!-- Allow cleartext specifically for the local server IP (if needed) -->
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="true">192.168.0.112</domain>
|
||||||
|
</domain-config>
|
||||||
|
</network-security-config>
|
||||||
|
|
||||||
Reference in New Issue
Block a user