diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aee9c35..0ff9cf5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -52,6 +52,13 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.ktor.client.core) implementation(libs.ktor.client.cio) + implementation(libs.androidx.camera.core) + implementation(libs.androidx.camera.camera2) + implementation(libs.androidx.camera.lifecycle) + implementation(libs.androidx.camera.video) + implementation(libs.androidx.camera.view) + implementation(libs.androidx.camera.mlkit.vision) + implementation(libs.androidx.camera.extensions) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5f93a75..cd71aa1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,10 +1,17 @@ + + + + + () override fun onCreate(savedInstanceState: Bundle?) { @@ -46,6 +51,8 @@ class MainActivity : ComponentActivity() { bluetoothService.onBluetoothStateChanged { state -> appViewModel.setBluetoothEnabled(state == BluetoothAdapter.STATE_ON) } appViewModel.setBluetoothEnabled(bluetoothService.isBluetoothEnabled()) deviceService = DeviceService(this.applicationContext) + cameraService = CameraService(this.applicationContext) + checkPermissions() enableEdgeToEdge() setContent { TVControllerTheme { @@ -57,6 +64,12 @@ class MainActivity : ComponentActivity() { } } } + + private fun checkPermissions() { + if (!cameraService.hasRequiredPermissions()) { + ActivityCompat.requestPermissions(this, CameraService.CAMERAX_PERMISSIONS, 0) + } + } } @Composable @@ -115,31 +128,19 @@ fun TvControllerApp( modifier = Modifier.padding(innerPadding) ) { composable(route = Screen.Camera.name) { - CameraScreen() + CameraView() } composable(route = Screen.Remote.name) { RemoteScreen() } composable(route = Screen.Settings.name) { - SettingsScreen(appViewModel = appViewModel, deviceService = deviceService) + SettingsView(appViewModel = appViewModel, deviceService = deviceService) } } } } } -@Composable -fun CameraScreen(modifier: Modifier = Modifier) { - Column( - modifier = modifier - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text(text = "Camera Screen", modifier = modifier) - } -} - @Composable fun RemoteScreen(modifier: Modifier = Modifier) { Column( diff --git a/app/src/main/java/com/example/tvcontroller/services/CameraService.kt b/app/src/main/java/com/example/tvcontroller/services/CameraService.kt new file mode 100644 index 0000000..910d91e --- /dev/null +++ b/app/src/main/java/com/example/tvcontroller/services/CameraService.kt @@ -0,0 +1,19 @@ +package com.example.tvcontroller.services + +import android.content.Context +import android.content.pm.PackageManager + +class CameraService(private val context: Context) { + fun hasRequiredPermissions(): Boolean { + return CAMERAX_PERMISSIONS.all { + context.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED + } + } + + companion object { + val CAMERAX_PERMISSIONS = arrayOf( + android.Manifest.permission.CAMERA, + android.Manifest.permission.RECORD_AUDIO + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tvcontroller/ui/components/CameraPreview.kt b/app/src/main/java/com/example/tvcontroller/ui/components/CameraPreview.kt new file mode 100644 index 0000000..e100c9e --- /dev/null +++ b/app/src/main/java/com/example/tvcontroller/ui/components/CameraPreview.kt @@ -0,0 +1,25 @@ +package com.example.tvcontroller.ui.components + +import androidx.camera.view.LifecycleCameraController +import androidx.camera.view.PreviewView +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.LocalLifecycleOwner + +@Composable +fun CameraPreview( + controller: LifecycleCameraController, + modifier: Modifier = Modifier +) { + val lifecycleOwner = LocalLifecycleOwner.current + AndroidView( + modifier = modifier, + factory = { + PreviewView(it).apply { + this.controller = controller + controller.bindToLifecycle(lifecycleOwner) + } + }, + ) +} diff --git a/app/src/main/java/com/example/tvcontroller/ui/views/CameraView.kt b/app/src/main/java/com/example/tvcontroller/ui/views/CameraView.kt new file mode 100644 index 0000000..02ab23f --- /dev/null +++ b/app/src/main/java/com/example/tvcontroller/ui/views/CameraView.kt @@ -0,0 +1,34 @@ +package com.example.tvcontroller.ui.views + +import androidx.camera.view.CameraController +import androidx.camera.view.LifecycleCameraController +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.example.tvcontroller.ui.components.CameraPreview + +@Composable +fun CameraView() { + val context = LocalContext.current + val controller = remember { + LifecycleCameraController(context).apply { + setEnabledUseCases(CameraController.VIDEO_CAPTURE) + } + } + Box( + modifier = Modifier + .fillMaxSize() + .padding(all = 16.dp), + ) { + CameraPreview(controller = controller, modifier = Modifier.fillMaxSize()) + } +} diff --git a/app/src/main/java/com/example/tvcontroller/SettingsScreen.kt b/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt similarity index 92% rename from app/src/main/java/com/example/tvcontroller/SettingsScreen.kt rename to app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt index 6a956ad..c1a54f5 100644 --- a/app/src/main/java/com/example/tvcontroller/SettingsScreen.kt +++ b/app/src/main/java/com/example/tvcontroller/ui/views/SettingsView.kt @@ -1,4 +1,4 @@ -package com.example.tvcontroller +package com.example.tvcontroller.ui.views import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -16,13 +16,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.tvcontroller.R +import com.example.tvcontroller.Settings +import com.example.tvcontroller.SettingsViewModel import com.example.tvcontroller.services.DeviceService import com.example.tvcontroller.ui.AppViewModel @Composable -fun SettingsScreen(deviceService: DeviceService, appViewModel: AppViewModel) { +fun SettingsView(deviceService: DeviceService, appViewModel: AppViewModel) { val viewModel = - viewModel(factory = SettingsViewModel.provideFactory(deviceService)) + viewModel(factory = SettingsViewModel.Companion.provideFactory(deviceService)) Column( modifier = Modifier diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c7146a4..460bb85 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.9.0" +cameraCore = "1.4.1" kotlin = "2.0.0" coreKtx = "1.10.1" junit = "4.13.2" @@ -12,6 +13,13 @@ composeBom = "2024.04.01" navigationCompose = "2.8.4" [libraries] +androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" } +androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCore" } +androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "cameraCore" } +androidx-camera-mlkit-vision = { module = "androidx.camera:camera-mlkit-vision", version.ref = "cameraCore" } +androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" } +androidx-camera-video = { module = "androidx.camera:camera-video", version.ref = "cameraCore" } +androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCore" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } junit = { group = "junit", name = "junit", version.ref = "junit" }