@@ -23,6 +23,7 @@ import android.graphics.Bitmap
2323import android.graphics.BitmapFactory
2424import android.graphics.Matrix
2525import android.net.Uri
26+ import android.util.Log
2627import androidx.activity.compose.rememberLauncherForActivityResult
2728import androidx.activity.result.PickVisualMediaRequest
2829import androidx.activity.result.contract.ActivityResultContracts
@@ -75,9 +76,11 @@ import androidx.compose.material3.TextField
7576import androidx.compose.material3.TextFieldDefaults
7677import androidx.compose.material3.rememberModalBottomSheetState
7778import androidx.compose.runtime.Composable
79+ import androidx.compose.runtime.DisposableEffect
7880import androidx.compose.runtime.LaunchedEffect
7981import androidx.compose.runtime.collectAsState
8082import androidx.compose.runtime.getValue
83+ import androidx.compose.runtime.mutableIntStateOf
8184import androidx.compose.runtime.mutableStateOf
8285import androidx.compose.runtime.remember
8386import androidx.compose.runtime.rememberCoroutineScope
@@ -104,6 +107,8 @@ import com.google.ai.edge.gallery.ui.theme.GalleryTheme
104107import kotlinx.coroutines.launch
105108import java.util.concurrent.Executors
106109
110+ private const val TAG = " AGMessageInputText"
111+
107112/* *
108113 * Composable function to display a text input field for composing chat messages.
109114 *
@@ -135,7 +140,7 @@ fun MessageInputText(
135140 var showAddContentMenu by remember { mutableStateOf(false ) }
136141 var showTextInputHistorySheet by remember { mutableStateOf(false ) }
137142 var showCameraCaptureBottomSheet by remember { mutableStateOf(false ) }
138- var cameraCaptureSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true )
143+ val cameraCaptureSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true )
139144 var tempPhotoUri by remember { mutableStateOf(value = Uri .EMPTY ) }
140145 var pickedImages by remember { mutableStateOf<List <Bitmap >>(listOf ()) }
141146 val updatePickedImages: (Bitmap ) -> Unit = { bitmap ->
@@ -150,21 +155,6 @@ fun MessageInputText(
150155 checkFrontCamera(context = context, callback = { hasFrontCamera = it })
151156 }
152157
153- // launches camera
154- val cameraLauncher =
155- rememberLauncherForActivityResult(ActivityResultContracts .TakePicture ()) { isImageSaved ->
156- if (isImageSaved) {
157- handleImageSelected(
158- context = context,
159- uri = tempPhotoUri,
160- onImageSelected = { bitmap ->
161- updatePickedImages(bitmap)
162- },
163- rotateForPortrait = true ,
164- )
165- }
166- }
167-
168158 // Permission request when taking picture.
169159 val takePicturePermissionLauncher = rememberLauncherForActivityResult(
170160 ActivityResultContracts .RequestPermission ()
@@ -173,7 +163,6 @@ fun MessageInputText(
173163 showAddContentMenu = false
174164 tempPhotoUri = context.createTempPictureUri()
175165 showCameraCaptureBottomSheet = true
176- // cameraLauncher.launch(tempPhotoUri)
177166 }
178167 }
179168
@@ -270,7 +259,6 @@ fun MessageInputText(
270259 showAddContentMenu = false
271260 tempPhotoUri = context.createTempPictureUri()
272261 showCameraCaptureBottomSheet = true
273- // cameraLauncher.launch(tempPhotoUri)
274262 }
275263
276264 // Otherwise, ask for permission
@@ -420,24 +408,26 @@ fun MessageInputText(
420408 var cameraProvider by remember { mutableStateOf<ProcessCameraProvider ?>(null ) }
421409 var cameraControl by remember { mutableStateOf<CameraControl ?>(null ) }
422410 val localContext = LocalContext .current
423- var cameraSide by remember { mutableStateOf(CameraSelector .LENS_FACING_BACK ) }
424-
411+ var cameraSide by remember { mutableIntStateOf(CameraSelector .LENS_FACING_BACK ) }
425412 val executor = remember { Executors .newSingleThreadExecutor() }
426- val capturedImageUri = remember { mutableStateOf<Uri ?>(null ) }
427413
428414 fun rebindCameraProvider () {
429415 cameraProvider?.let { cameraProvider ->
430416 val cameraSelector = CameraSelector .Builder ()
431417 .requireLensFacing(cameraSide)
432418 .build()
433- cameraProvider.unbindAll()
434- val camera = cameraProvider.bindToLifecycle(
435- lifecycleOwner = lifecycleOwner,
436- cameraSelector = cameraSelector,
437- previewUseCase,
438- imageCaptureUseCase
439- )
440- cameraControl = camera.cameraControl
419+ try {
420+ cameraProvider.unbindAll()
421+ val camera = cameraProvider.bindToLifecycle(
422+ lifecycleOwner = lifecycleOwner,
423+ cameraSelector = cameraSelector,
424+ previewUseCase,
425+ imageCaptureUseCase
426+ )
427+ cameraControl = camera.cameraControl
428+ } catch (e: Exception ) {
429+ Log .d(TAG , " Failed to bind camera" , e)
430+ }
441431 }
442432 }
443433
@@ -450,31 +440,25 @@ fun MessageInputText(
450440 rebindCameraProvider()
451441 }
452442
453- // val cameraController = remember {
454- // LifecycleCameraController(context).apply {
455- // bindToLifecycle(lifecycleOwner)
456- // }
457- // }
443+ DisposableEffect (Unit ) { // Or key on lifecycleOwner if it makes more sense
444+ onDispose {
445+ cameraProvider?.unbindAll() // Unbind all use cases from the camera provider
446+ if (! executor.isShutdown) {
447+ executor.shutdown() // Shut down the executor service
448+ }
449+ }
450+ }
458451
459452 Box (modifier = Modifier .fillMaxSize()) {
460453 // PreviewView for the camera feed.
461454 AndroidView (
462455 modifier = Modifier .fillMaxSize(),
463456 factory = { ctx ->
464- PreviewView (context ).also {
457+ PreviewView (ctx ).also {
465458 previewUseCase.surfaceProvider = it.surfaceProvider
466459 rebindCameraProvider()
467460 }
468- // PreviewView(ctx).apply {
469- // scaleType = PreviewView.ScaleType.FILL_START
470- // implementationMode = PreviewView.ImplementationMode.COMPATIBLE
471- // controller = cameraController // Attach the lifecycle-aware camera controller.
472- // }
473461 },
474- // onRelease = {
475- // // Called when the PreviewView is removed from the composable hierarchy
476- // cameraController.unbind() // Unbinds the camera to free up resources
477- // }
478462 )
479463
480464 // Close button.
@@ -508,9 +492,9 @@ fun MessageInputText(
508492 .size(64 .dp)
509493 .border(2 .dp, MaterialTheme .colorScheme.onPrimary, CircleShape ),
510494 onClick = {
511- scope.launch {
512- val callback = object : ImageCapture . OnImageCapturedCallback ( ) {
513- override fun onCaptureSuccess ( image : ImageProxy ) {
495+ val callback = object : ImageCapture . OnImageCapturedCallback () {
496+ override fun onCaptureSuccess ( image : ImageProxy ) {
497+ try {
514498 var bitmap = image.toBitmap()
515499 val rotation = image.imageInfo.rotationDegrees
516500 bitmap = if (rotation != 0 ) {
@@ -520,13 +504,18 @@ fun MessageInputText(
520504 Bitmap .createBitmap(bitmap, 0 , 0 , bitmap.width, bitmap.height, matrix, true )
521505 } else bitmap
522506 updatePickedImages(bitmap)
507+ } catch (e: Exception ) {
508+ Log .e(TAG , " Failed to process image" , e)
509+ } finally {
523510 image.close()
511+ scope.launch {
512+ cameraCaptureSheetState.hide()
513+ showCameraCaptureBottomSheet = false
514+ }
524515 }
525516 }
526- imageCaptureUseCase.takePicture(executor, callback)
527- cameraCaptureSheetState.hide()
528- showCameraCaptureBottomSheet = false
529517 }
518+ imageCaptureUseCase.takePicture(executor, callback)
530519 },
531520 ) {
532521 Icon (
0 commit comments