-
Notifications
You must be signed in to change notification settings - Fork 470
[CameraX • Compose] Update CameraXBasic to match compose best practices #300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.height | |||||||||||||
| import androidx.compose.foundation.layout.offset | ||||||||||||||
| import androidx.compose.foundation.layout.padding | ||||||||||||||
| import androidx.compose.foundation.layout.size | ||||||||||||||
| import androidx.compose.foundation.layout.wrapContentSize | ||||||||||||||
| import androidx.compose.foundation.shape.CircleShape | ||||||||||||||
| import androidx.compose.material.icons.Icons | ||||||||||||||
| import androidx.compose.material.icons.automirrored.filled.ArrowBack | ||||||||||||||
|
|
@@ -63,6 +64,7 @@ import androidx.compose.ui.unit.round | |||||||||||||
| import androidx.lifecycle.LifecycleOwner | ||||||||||||||
| import androidx.lifecycle.compose.LocalLifecycleOwner | ||||||||||||||
| import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||||||||||||||
| import androidx.lifecycle.viewmodel.compose.viewModel | ||||||||||||||
| import coil.compose.rememberAsyncImagePainter | ||||||||||||||
| import com.google.accompanist.permissions.ExperimentalPermissionsApi | ||||||||||||||
| import com.google.accompanist.permissions.PermissionState | ||||||||||||||
|
|
@@ -84,22 +86,21 @@ fun CameraXBasic(modifier: Modifier = Modifier) { | |||||||||||||
| var showCapturedImage by remember { mutableStateOf<Uri?>(null) } | ||||||||||||||
| val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA) | ||||||||||||||
| val imageCaptureCallbackExecutor: ExecutorService = remember { Executors.newSingleThreadExecutor() } | ||||||||||||||
| val viewModel = remember { CameraXBasicViewModel() } | ||||||||||||||
| val viewModel: CameraXBasicViewModel = viewModel() | ||||||||||||||
|
|
||||||||||||||
| DisposableEffect(Unit) { | ||||||||||||||
| onDispose { | ||||||||||||||
| imageCaptureCallbackExecutor.shutdown() | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| Box(modifier = Modifier.fillMaxSize()) { | ||||||||||||||
| Box(modifier = modifier.fillMaxSize()) { | ||||||||||||||
| ContentWithPermissionHandling( | ||||||||||||||
| cameraPermissionState = cameraPermissionState, | ||||||||||||||
| showCapturedImage = showCapturedImage, | ||||||||||||||
| onShowCapturedImageChange = { showCapturedImage = it }, | ||||||||||||||
| viewModel = viewModel, | ||||||||||||||
| imageCaptureCallbackExecutor = imageCaptureCallbackExecutor, | ||||||||||||||
| modifier = modifier, | ||||||||||||||
| ) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -131,7 +132,10 @@ private fun ContentWithPermissionHandling( | |||||||||||||
| when (cameraPermissionState.status) { | ||||||||||||||
| is PermissionStatus.Granted -> { | ||||||||||||||
| if (showCapturedImage != null) { | ||||||||||||||
| CapturedImageView(uri = showCapturedImage) { | ||||||||||||||
| CapturedImageView( | ||||||||||||||
| uri = showCapturedImage, | ||||||||||||||
| modifier = modifier, | ||||||||||||||
| ) { | ||||||||||||||
| onShowCapturedImageChange(null) | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
|
|
@@ -154,6 +158,7 @@ private fun ContentWithPermissionHandling( | |||||||||||||
| is PermissionStatus.Denied -> CameraPermissionDeniedView( | ||||||||||||||
| cameraPermissionState.status, | ||||||||||||||
| cameraPermissionState, | ||||||||||||||
| modifier = modifier, | ||||||||||||||
| ) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -171,9 +176,10 @@ private fun ContentWithPermissionHandling( | |||||||||||||
| private fun CameraPermissionDeniedView( | ||||||||||||||
| status: PermissionStatus, | ||||||||||||||
| cameraPermissionState: PermissionState, | ||||||||||||||
| modifier: Modifier, | ||||||||||||||
| ) { | ||||||||||||||
| Column( | ||||||||||||||
| modifier = Modifier | ||||||||||||||
| modifier = modifier | ||||||||||||||
| .fillMaxSize() | ||||||||||||||
| .padding(16.dp), | ||||||||||||||
| horizontalAlignment = Alignment.CenterHorizontally, | ||||||||||||||
|
|
@@ -185,7 +191,7 @@ private fun CameraPermissionDeniedView( | |||||||||||||
| "Camera permission is required to use this feature." | ||||||||||||||
| } | ||||||||||||||
| Text(text = textToShow) | ||||||||||||||
| Spacer(modifier = Modifier.height(8.dp)) | ||||||||||||||
| Spacer(modifier = modifier.height(8.dp)) | ||||||||||||||
| Button(onClick = { cameraPermissionState.launchPermissionRequest() }) { | ||||||||||||||
| Text("Request Permission") | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -232,11 +238,11 @@ private fun CameraPreviewContent( | |||||||||||||
|
|
||||||||||||||
| surfaceRequest?.let { request -> | ||||||||||||||
| val coordinateTransformer = remember { MutableCoordinateTransformer() } | ||||||||||||||
| Box(modifier = Modifier.fillMaxSize()) { | ||||||||||||||
| Box(modifier = modifier.fillMaxSize()) { | ||||||||||||||
| CameraXViewfinder( | ||||||||||||||
| surfaceRequest = request, | ||||||||||||||
| coordinateTransformer = coordinateTransformer, | ||||||||||||||
| modifier = Modifier | ||||||||||||||
| modifier = modifier | ||||||||||||||
| .fillMaxSize() // Ensure CameraXViewfinder fills the Box | ||||||||||||||
| .pointerInput(viewModel, coordinateTransformer) { | ||||||||||||||
| detectTapGestures { tapCoords -> | ||||||||||||||
|
|
@@ -253,7 +259,7 @@ private fun CameraPreviewContent( | |||||||||||||
| visible = showAutofocusIndicator, | ||||||||||||||
| enter = fadeIn(), | ||||||||||||||
| exit = fadeOut(), | ||||||||||||||
| modifier = Modifier | ||||||||||||||
| modifier = modifier | ||||||||||||||
| .offset { autofocusLocation.takeOrElse { Offset.Zero }.round() } | ||||||||||||||
| .offset((-24).dp, (-24).dp), // Adjust offset to center the indicator | ||||||||||||||
|
Comment on lines
+262
to
264
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the To ensure predictable behavior and proper encapsulation, should this
Suggested change
|
||||||||||||||
| ) { | ||||||||||||||
|
|
@@ -263,14 +269,14 @@ private fun CameraPreviewContent( | |||||||||||||
| .size(48.dp), | ||||||||||||||
| ) | ||||||||||||||
| } | ||||||||||||||
| Column( | ||||||||||||||
| modifier = Modifier | ||||||||||||||
| .align(Alignment.BottomCenter) | ||||||||||||||
|
|
||||||||||||||
| Button( | ||||||||||||||
| modifier = modifier | ||||||||||||||
| .fillMaxSize() | ||||||||||||||
| .wrapContentSize(align = Alignment.BottomCenter) | ||||||||||||||
| .padding(16.dp), | ||||||||||||||
|
Comment on lines
+274
to
277
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The For clarity and to avoid unintended side effects from the passed-in modifier = Modifier
.fillMaxSize()
.wrapContentSize(align = Alignment.BottomCenter)
.padding(16.dp) |
||||||||||||||
| horizontalAlignment = Alignment.CenterHorizontally, | ||||||||||||||
| ) { | ||||||||||||||
| Button(onClick = onTakePhotoClick) { Text("Take Photo") } | ||||||||||||||
| } | ||||||||||||||
| onClick = onTakePhotoClick, | ||||||||||||||
| ) { Text("Take Photo") } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -283,19 +289,23 @@ private fun CameraPreviewContent( | |||||||||||||
| * (e.g., clicks the back button). | ||||||||||||||
| */ | ||||||||||||||
| @Composable | ||||||||||||||
| fun CapturedImageView(uri: Uri, onDismiss: () -> Unit) { | ||||||||||||||
| fun CapturedImageView( | ||||||||||||||
| uri: Uri, | ||||||||||||||
| modifier: Modifier, | ||||||||||||||
| onDismiss: () -> Unit, | ||||||||||||||
| ) { | ||||||||||||||
| Box( | ||||||||||||||
| modifier = Modifier.fillMaxSize(), | ||||||||||||||
| modifier = modifier.fillMaxSize(), | ||||||||||||||
| ) { | ||||||||||||||
| Image( | ||||||||||||||
| painter = rememberAsyncImagePainter(uri), | ||||||||||||||
| contentDescription = "Captured Image", | ||||||||||||||
| modifier = Modifier.fillMaxSize(), | ||||||||||||||
| modifier = modifier.fillMaxSize(), | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The To avoid such compounded effects, should the modifier = Modifier.fillMaxSize() |
||||||||||||||
| contentScale = ContentScale.Fit, | ||||||||||||||
| ) | ||||||||||||||
| IconButton( | ||||||||||||||
| onClick = onDismiss, | ||||||||||||||
| modifier = Modifier | ||||||||||||||
| modifier = modifier | ||||||||||||||
| .align(Alignment.TopStart) | ||||||||||||||
| .padding(16.dp), | ||||||||||||||
|
Comment on lines
+308
to
310
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the Would it be clearer and safer to use modifier = Modifier
.align(Alignment.TopStart)
.padding(16.dp) |
||||||||||||||
| ) { | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the
modifierparameter ofCameraPermissionDeniedViewis being applied to thisSpacer. Typically, themodifierparameter of a composable is intended for its root element (theColumnin this case). Child elements like thisSpacershould define their own modifiers.Reusing the parent's
modifierhere could lead to unexpected behavior. For example, ifCameraPermissionDeniedViewwere called withmodifier = Modifier.fillMaxWidth(), thisSpacerwould also attempt tofillMaxWidth, which is likely not the desired outcome for a simple spacer.Could this be changed to use
Modifier.height(8.dp)directly?