diff --git a/app-android/build.gradle.kts b/app-android/build.gradle.kts index 23dd7db..2cdf104 100644 --- a/app-android/build.gradle.kts +++ b/app-android/build.gradle.kts @@ -12,7 +12,7 @@ android { compileSdk = 36 defaultConfig { applicationId = "dev.sdkforge.camera.android" - minSdk = 21 + minSdk = 23 targetSdk = 36 versionCode = 1 versionName = "1.0" diff --git a/app-android/src/main/java/dev/sdkforge/camera/android/MainActivity.kt b/app-android/src/main/java/dev/sdkforge/camera/android/MainActivity.kt index 6608be4..5f907ac 100644 --- a/app-android/src/main/java/dev/sdkforge/camera/android/MainActivity.kt +++ b/app-android/src/main/java/dev/sdkforge/camera/android/MainActivity.kt @@ -11,12 +11,16 @@ import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateSetOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import dev.sdkforge.camera.app.App +import dev.sdkforge.camera.app.AppConstants import dev.sdkforge.camera.domain.CameraConfig import dev.sdkforge.camera.domain.Facing import dev.sdkforge.camera.domain.Format +import dev.sdkforge.camera.domain.ScanResult import dev.sdkforge.camera.ui.rememberCameraController class MainActivity : ComponentActivity() { @@ -52,16 +56,25 @@ class MainActivity : ComponentActivity() { cameraFacing = Facing.BACK, ), ) - + val scans = remember { mutableStateSetOf() } App( cameraController = cameraController, + scans = scans, modifier = Modifier .fillMaxSize(), ) LaunchedEffect(Unit) { cameraController.scannedResults.collect { scanResult -> - toast("${scanResult.format.name}; value = ${scanResult.value}") + if (scans.contains(scanResult)) { + toast("${AppConstants.SUCCESS_TITLE} - ${AppConstants.ALREADY_SCANNED_MESSAGE}") + } else { + if (scans.size == AppConstants.HISTORY_SCANS_MAX_LENGTH) { + scans.remove(scans.first()) + } + scans.add(scanResult) + toast("${AppConstants.SUCCESS_TITLE} - ${AppConstants.SCANNED_VALUE} ${scanResult.value}") + } } } diff --git a/app-shared/build.gradle.kts b/app-shared/build.gradle.kts index db18eb8..37c6fe3 100644 --- a/app-shared/build.gradle.kts +++ b/app-shared/build.gradle.kts @@ -15,6 +15,7 @@ kotlin { api(compose.foundation) api(compose.material3) + api(compose.materialIconsExtended) } } } diff --git a/app-shared/src/commonMain/kotlin/dev/sdkforge/camera/app/App.kt b/app-shared/src/commonMain/kotlin/dev/sdkforge/camera/app/App.kt index 4398be9..6be61eb 100644 --- a/app-shared/src/commonMain/kotlin/dev/sdkforge/camera/app/App.kt +++ b/app-shared/src/commonMain/kotlin/dev/sdkforge/camera/app/App.kt @@ -5,21 +5,31 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import dev.sdkforge.camera.domain.ScanResult import dev.sdkforge.camera.ui.CameraController -import dev.sdkforge.camera.ui.CameraView @Composable fun App( cameraController: CameraController, + scans: Set, modifier: Modifier = Modifier, ) = ApplicationTheme { Surface( modifier = modifier, color = MaterialTheme.colorScheme.background, ) { - CameraView( + ScannerScreen( cameraController = cameraController, + scans = scans, modifier = Modifier.fillMaxSize(), ) } } + +object AppConstants { + const val HISTORY_SCANS_MAX_LENGTH = 5 + const val ERROR_TITLE = "Error" + const val SUCCESS_TITLE = "Success" + const val ALREADY_SCANNED_MESSAGE = "Already scanned, check scans history" + const val SCANNED_VALUE = "Scanned value:" +} diff --git a/app-shared/src/commonMain/kotlin/dev/sdkforge/camera/app/ScannerScreen.kt b/app-shared/src/commonMain/kotlin/dev/sdkforge/camera/app/ScannerScreen.kt new file mode 100644 index 0000000..9926e99 --- /dev/null +++ b/app-shared/src/commonMain/kotlin/dev/sdkforge/camera/app/ScannerScreen.kt @@ -0,0 +1,156 @@ +package dev.sdkforge.camera.app + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.FlashOff +import androidx.compose.material.icons.filled.FlashOn +import androidx.compose.material.icons.filled.FlipCameraAndroid +import androidx.compose.material.icons.filled.History +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import dev.sdkforge.camera.domain.ScanResult +import dev.sdkforge.camera.ui.CameraController +import dev.sdkforge.camera.ui.CameraView + +@Composable +fun ScannerScreen( + cameraController: CameraController, + scans: Set, + modifier: Modifier = Modifier, +) { + var isHistoryDialogShown by remember { mutableStateOf(false) } + + Scaffold( + modifier = Modifier.fillMaxSize(), + ) { innerPadding -> + CameraView( + cameraController = cameraController, + modifier = Modifier.fillMaxSize(), + ) + ButtonsOverlay( + controller = cameraController, + modifier = modifier.padding(top = innerPadding.calculateTopPadding()), + onHistoryClicked = { isHistoryDialogShown = !isHistoryDialogShown }, + ) + ScansHistoryDialog( + showDialog = isHistoryDialogShown, + scans = scans, + onDismiss = { isHistoryDialogShown = false }, + ) + } +} + +@Composable +fun ButtonsOverlay( + controller: CameraController, + modifier: Modifier = Modifier, + onHistoryClicked: () -> Unit, +) { + var isFlashOn by remember { mutableStateOf(false) } + val targetIcon = if (isFlashOn) Icons.Default.FlashOff else Icons.Default.FlashOn + + Column { + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.Top, + modifier = modifier.fillMaxWidth(), + ) { + IconButton( + onClick = { + onHistoryClicked.invoke() + }, + ) { + Icon( + imageVector = Icons.Default.History, + contentDescription = "Show scans history", + modifier = Modifier.clickable { + onHistoryClicked.invoke() + }, + ) + } + IconButton( + onClick = { + controller.toggleFlash() + isFlashOn = controller.isFlashIsOn() + }, + ) { + Icon( + imageVector = targetIcon, + contentDescription = "Flash toggle", + ) + } + IconButton( + onClick = { + controller.toggleActiveCamera() + }, + ) { + Icon( + imageVector = Icons.Default.FlipCameraAndroid, + contentDescription = "Flip between front and back active cameras", + ) + } + } + } +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +fun ScansHistoryDialog( + showDialog: Boolean, + scans: Set, + onDismiss: () -> Unit, +) { + val sheetState = rememberModalBottomSheetState() + if (showDialog) { + ModalBottomSheet( + onDismissRequest = { + onDismiss.invoke() + }, + sheetState = sheetState, + modifier = Modifier.fillMaxSize(), + ) { + if (scans.isEmpty()) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + Text( + text = "Scans history is empty", + ) + } + } else { + LazyColumn( + modifier = Modifier.padding(8.dp).fillMaxWidth(), + ) { + itemsIndexed(items = scans.toList()) { index, item -> + Text( + text = "${index + 1}. ${item.value}", + modifier = Modifier.padding(top = 12.dp), + ) + } + } + } + } + } +} diff --git a/app-shared/src/iosMain/kotlin/dev/sdkforge/camera/app/ComposeAppViewController.kt b/app-shared/src/iosMain/kotlin/dev/sdkforge/camera/app/ComposeAppViewController.kt index b1863f3..9e77798 100644 --- a/app-shared/src/iosMain/kotlin/dev/sdkforge/camera/app/ComposeAppViewController.kt +++ b/app-shared/src/iosMain/kotlin/dev/sdkforge/camera/app/ComposeAppViewController.kt @@ -2,12 +2,15 @@ package dev.sdkforge.camera.app import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateSetOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.uikit.LocalUIViewController import androidx.compose.ui.window.ComposeUIViewController import dev.sdkforge.camera.domain.CameraConfig import dev.sdkforge.camera.domain.Facing import dev.sdkforge.camera.domain.Format +import dev.sdkforge.camera.domain.ScanResult import dev.sdkforge.camera.ui.rememberCameraController import kotlin.experimental.ExperimentalObjCName import platform.AVFoundation.AVCaptureDevice @@ -37,18 +40,28 @@ fun ComposeAppViewController() = ComposeUIViewController( cameraFacing = Facing.BACK, ), ) - + val scans = remember { mutableStateSetOf() } App( cameraController = cameraController, + scans = scans, modifier = Modifier .fillMaxSize(), ) LaunchedEffect(Unit) { cameraController.scannedResults.collect { scanResult -> + val alertText = if (scans.contains(scanResult)) { + AppConstants.ERROR_TITLE to AppConstants.ALREADY_SCANNED_MESSAGE + } else { + if (scans.size == AppConstants.HISTORY_SCANS_MAX_LENGTH) { + scans.remove(scans.first()) + } + scans.add(scanResult) + AppConstants.SUCCESS_TITLE to "${AppConstants.SCANNED_VALUE} ${scanResult.value}" + } val alert = UIAlertController.alertControllerWithTitle( - title = "Scanned format: ${scanResult.format}", - message = "Scanned value: ${scanResult.value}", + title = alertText.first, + message = alertText.second, preferredStyle = UIAlertControllerStyleAlert, ).apply { addAction( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 13dcc8a..0327437 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,9 @@ runner = "1.7.0" junit = "1.3.0" junitVersion = "4.13.2" benchmarkJunit4 = "1.4.0" +androidxCamera = "1.5.0" +barcodeScanning = "17.3.0" +barcodeServices = "18.3.1" [libraries] android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } @@ -30,6 +33,10 @@ androidx-runner = { group = "androidx.test", name = "runner", version.ref = "run androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junitVersion" } androidx-benchmark-junit4 = { group = "androidx.benchmark", name = "benchmark-junit4", version.ref = "benchmarkJunit4" } +androidx-camera-camera = { group = "androidx.camera", name="camera-camera2", version.ref = "androidxCamera" } +androidx-camera-view = { group = "androidx.camera", name = "camera-view",version.ref = "androidxCamera" } +barcode-scanning = { group = "com.google.mlkit", name = "barcode-scanning", version.ref = "barcodeScanning" } +barcode-services = { group = "com.google.android.gms", name = "play-services-mlkit-barcode-scanning", version.ref = "barcodeServices" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/shared-ui/api/shared-ui.api b/shared-ui/api/shared-ui.api index 83278da..3fcfe01 100644 --- a/shared-ui/api/shared-ui.api +++ b/shared-ui/api/shared-ui.api @@ -3,6 +3,9 @@ public abstract class dev/sdkforge/camera/ui/CameraController { public fun ()V public abstract fun getCameraState ()Ldev/sdkforge/camera/ui/CameraState; public abstract fun getScannedResults ()Lkotlinx/coroutines/flow/Flow; + public abstract fun isFlashIsOn ()Z + public abstract fun toggleActiveCamera ()V + public abstract fun toggleFlash ()V } public abstract interface class dev/sdkforge/camera/ui/CameraState { diff --git a/shared-ui/build.gradle.kts b/shared-ui/build.gradle.kts index 6de57c4..5a564a0 100644 --- a/shared-ui/build.gradle.kts +++ b/shared-ui/build.gradle.kts @@ -28,10 +28,10 @@ kotlin { androidMain { dependencies { - implementation("androidx.camera:camera-camera2:1.4.2") - implementation("androidx.camera:camera-view:1.4.2") - implementation("com.google.mlkit:barcode-scanning:17.3.0") - implementation("com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1") + implementation(libs.androidx.camera.camera) + implementation(libs.androidx.camera.view) + implementation(libs.barcode.scanning) + implementation(libs.barcode.services) } } } diff --git a/shared-ui/dependencies/releaseRuntimeClasspath.txt b/shared-ui/dependencies/releaseRuntimeClasspath.txt index a23cd71..8f30308 100644 --- a/shared-ui/dependencies/releaseRuntimeClasspath.txt +++ b/shared-ui/dependencies/releaseRuntimeClasspath.txt @@ -9,11 +9,12 @@ androidx.appcompat:appcompat:1.6.1 androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 androidx.autofill:autofill:1.0.0 -androidx.camera:camera-camera2:1.4.2 -androidx.camera:camera-core:1.4.2 -androidx.camera:camera-lifecycle:1.4.2 -androidx.camera:camera-video:1.4.2 -androidx.camera:camera-view:1.4.2 +androidx.camera.featurecombinationquery:featurecombinationquery:1.5.0 +androidx.camera:camera-camera2:1.5.0 +androidx.camera:camera-core:1.5.0 +androidx.camera:camera-lifecycle:1.5.0 +androidx.camera:camera-video:1.5.0 +androidx.camera:camera-view:1.5.0 androidx.collection:collection-jvm:1.5.0 androidx.collection:collection-ktx:1.5.0 androidx.collection:collection:1.5.0 @@ -133,9 +134,9 @@ org.jetbrains.compose.ui:ui-unit:1.8.2 org.jetbrains.compose.ui:ui-util:1.8.2 org.jetbrains.compose.ui:ui:1.8.2 org.jetbrains.kotlin:kotlin-stdlib:2.2.20-Beta2 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 org.jspecify:jspecify:1.0.0 diff --git a/shared-ui/src/androidMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.android.kt b/shared-ui/src/androidMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.android.kt index 8046e54..d608156 100644 --- a/shared-ui/src/androidMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.android.kt +++ b/shared-ui/src/androidMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.android.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:function-expression-body") + package dev.sdkforge.camera.ui import android.content.Context @@ -5,6 +7,7 @@ import android.graphics.Color import androidx.annotation.OptIn import androidx.camera.core.CameraSelector import androidx.camera.core.ExperimentalGetImage +import androidx.camera.core.TorchState import androidx.camera.view.LifecycleCameraController import androidx.camera.view.PreviewView import androidx.core.content.ContextCompat @@ -169,4 +172,22 @@ internal actual class PlatformCameraView( internal actual fun onRelease() { controller.unbind() } + + internal actual fun toggleFlash() { + val currentTorchState = controller.torchState.value + controller.enableTorch(currentTorchState != TorchState.ON) + } + + internal actual fun isFlashIsOn(): Boolean { + return controller.torchState.value == TorchState.ON + } + + internal actual fun toggleActiveCamera() { + val currentCameraSelector = controller.cameraSelector + controller.cameraSelector = when (currentCameraSelector) { + CameraSelector.DEFAULT_BACK_CAMERA -> CameraSelector.DEFAULT_FRONT_CAMERA + CameraSelector.DEFAULT_FRONT_CAMERA -> CameraSelector.DEFAULT_BACK_CAMERA + else -> CameraSelector.DEFAULT_BACK_CAMERA + } + } } diff --git a/shared-ui/src/commonMain/kotlin/dev/sdkforge/camera/ui/CameraController.kt b/shared-ui/src/commonMain/kotlin/dev/sdkforge/camera/ui/CameraController.kt index 696d47b..e39a1fd 100644 --- a/shared-ui/src/commonMain/kotlin/dev/sdkforge/camera/ui/CameraController.kt +++ b/shared-ui/src/commonMain/kotlin/dev/sdkforge/camera/ui/CameraController.kt @@ -1,4 +1,4 @@ -@file:Suppress("ktlint:standard:class-signature") +@file:Suppress("ktlint:standard:class-signature", "ktlint:standard:function-expression-body") package dev.sdkforge.camera.ui @@ -8,6 +8,7 @@ import dev.sdkforge.camera.domain.CameraConfig import dev.sdkforge.camera.domain.ScanResult import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow /** * Platform-specific implementation of the native camera controller. @@ -59,7 +60,7 @@ internal abstract class NativeCameraController( * This mutable shared flow is used internally to emit scan results * and can be shared with multiple collectors. */ - protected val initialScannedResults: MutableSharedFlow = MutableSharedFlow() + protected val initialScannedResults: MutableSharedFlow = MutableSharedFlow(replay = 1) /** * Public flow of scanned results. @@ -67,7 +68,7 @@ internal abstract class NativeCameraController( * This flow emits [ScanResult] objects whenever a barcode is successfully * scanned and decoded. */ - override val scannedResults: Flow = initialScannedResults + override val scannedResults: SharedFlow = initialScannedResults /** * The initial camera state implementation. @@ -138,6 +139,18 @@ internal abstract class NativeCameraController( override fun onRelease() { platformCameraView.onRelease() } + + override fun toggleFlash() { + platformCameraView.toggleFlash() + } + + override fun isFlashIsOn(): Boolean { + return platformCameraView.isFlashIsOn() + } + + override fun toggleActiveCamera() { + platformCameraView.toggleActiveCamera() + } } /** @@ -195,4 +208,23 @@ abstract class CameraController { * Implementations should properly clean up camera resources. */ internal abstract fun onRelease() + + /** + * Changes state of camera flash to opposite of current. + * + * Provides control of flash in torch mode only. + */ + abstract fun toggleFlash() + + /** + * Check for flash is currently on in torch mode. + */ + abstract fun isFlashIsOn(): Boolean + + /** + * Changes what camera is active at the moment. + * + * Provides control of what camera, frontal or back, is currently active. + */ + abstract fun toggleActiveCamera() } diff --git a/shared-ui/src/commonMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.kt b/shared-ui/src/commonMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.kt index c301775..40ef1c7 100644 --- a/shared-ui/src/commonMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.kt +++ b/shared-ui/src/commonMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.kt @@ -46,4 +46,23 @@ internal expect class PlatformCameraView { * properly clean up camera resources and stop the preview. */ internal fun onRelease() + + /** + * Changes state of camera flash to opposite of current. + * + * Provides control of flash in torch mode only. + */ + internal fun toggleFlash() + + /** + * Check for flash is currently on in torch mode. + */ + internal fun isFlashIsOn(): Boolean + + /** + * Changes what camera is active at the moment. + * + * Provides control of what camera, frontal or back, is currently active. + */ + internal fun toggleActiveCamera() } diff --git a/shared-ui/src/iosMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.ios.kt b/shared-ui/src/iosMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.ios.kt index ca1fd4a..24688d3 100644 --- a/shared-ui/src/iosMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.ios.kt +++ b/shared-ui/src/iosMain/kotlin/dev/sdkforge/camera/ui/PlatformCameraView.ios.kt @@ -30,7 +30,9 @@ import platform.AVFoundation.automaticallyEnablesLowLightBoostWhenAvailable import platform.AVFoundation.defaultDeviceWithDeviceType import platform.AVFoundation.isAutoFocusRangeRestrictionSupported import platform.AVFoundation.isLowLightBoostSupported +import platform.AVFoundation.isTorchAvailable import platform.AVFoundation.isTorchModeSupported +import platform.AVFoundation.setFlashMode import platform.AVFoundation.torchMode import platform.CoreGraphics.CGRectZero import platform.QuartzCore.CALayer @@ -163,7 +165,6 @@ internal actual class PlatformCameraView( preferFrontCamera: Boolean, ): AVCaptureDevice? { val preferredPosition = if (preferFrontCamera) AVCaptureDevicePositionFront else AVCaptureDevicePositionBack - return AVCaptureDevice.defaultDeviceWithDeviceType( deviceType = AVCaptureDeviceTypeBuiltInTripleCamera, mediaType = AVMediaTypeVideo, @@ -265,4 +266,61 @@ internal actual class PlatformCameraView( internal actual fun onRelease() { captureSession.stopRunning() } + + /** + * Changes state of camera flash to opposite of current. + * + * Provides control of flash in torch mode only. + */ + internal actual fun toggleFlash() { + val device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) + if (device?.isTorchAvailable() == true) { + val targetMode = when (device.torchMode) { + AVCaptureTorchModeOn -> AVCaptureTorchModeOff + AVCaptureTorchModeOff -> AVCaptureTorchModeOn + else -> AVCaptureTorchModeAuto + } + device.setFlashMode(targetMode) + } + } + + /** + * Check for flash is currently on in torch mode. + */ + internal actual fun isFlashIsOn(): Boolean { + val device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) ?: return false + return device.isTorchAvailable() && device.torchMode == AVCaptureTorchModeOn + } + + /** + * Changes what camera is active at the moment. + * + * Provides control of what camera, frontal or back, is currently active. + */ + internal actual fun toggleActiveCamera() { + val frontCameraDeviceInput = AVCaptureDevice.defaultDeviceWithDeviceType( + AVCaptureDeviceTypeBuiltInTripleCamera, + AVMediaTypeVideo, + AVCaptureDevicePositionFront, + ) as? AVCaptureDeviceInput ?: return + val backCameraDeviceInput = AVCaptureDevice.defaultDeviceWithDeviceType( + AVCaptureDeviceTypeBuiltInTripleCamera, + AVMediaTypeVideo, + AVCaptureDevicePositionBack, + ) as? AVCaptureDeviceInput ?: return + + captureSession.apply { + beginConfiguration() + + if (inputs.contains(frontCameraDeviceInput)) { + removeInput(frontCameraDeviceInput) + addInput(backCameraDeviceInput) + } else if (inputs.contains(backCameraDeviceInput)) { + removeInput(backCameraDeviceInput) + addInput(frontCameraDeviceInput) + } + + commitConfiguration() + } + } } diff --git a/shared/dependencies/releaseRuntimeClasspath.txt b/shared/dependencies/releaseRuntimeClasspath.txt index a23cd71..8f30308 100644 --- a/shared/dependencies/releaseRuntimeClasspath.txt +++ b/shared/dependencies/releaseRuntimeClasspath.txt @@ -9,11 +9,12 @@ androidx.appcompat:appcompat:1.6.1 androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 androidx.autofill:autofill:1.0.0 -androidx.camera:camera-camera2:1.4.2 -androidx.camera:camera-core:1.4.2 -androidx.camera:camera-lifecycle:1.4.2 -androidx.camera:camera-video:1.4.2 -androidx.camera:camera-view:1.4.2 +androidx.camera.featurecombinationquery:featurecombinationquery:1.5.0 +androidx.camera:camera-camera2:1.5.0 +androidx.camera:camera-core:1.5.0 +androidx.camera:camera-lifecycle:1.5.0 +androidx.camera:camera-video:1.5.0 +androidx.camera:camera-view:1.5.0 androidx.collection:collection-jvm:1.5.0 androidx.collection:collection-ktx:1.5.0 androidx.collection:collection:1.5.0 @@ -133,9 +134,9 @@ org.jetbrains.compose.ui:ui-unit:1.8.2 org.jetbrains.compose.ui:ui-util:1.8.2 org.jetbrains.compose.ui:ui:1.8.2 org.jetbrains.kotlin:kotlin-stdlib:2.2.20-Beta2 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 org.jspecify:jspecify:1.0.0