Skip to content

Instantly share code, notes, and snippets.

@JolandaVerhoef
Last active April 25, 2024 12:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JolandaVerhoef/99dafc1861226969ca2f2fd374abb3e8 to your computer and use it in GitHub Desktop.
Save JolandaVerhoef/99dafc1861226969ca2f2fd374abb3e8 to your computer and use it in GitHub Desktop.
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Composable
fun TakePicture_WithCameraApp(modifier: Modifier = Modifier) {
// #1: Set the location where the image will be stored
val context = LocalContext.current
val uri = remember {
FileProvider.getUriForFile(
context,
context.applicationContext.packageName + ".provider",
File(context.filesDir, "image.jpg")
)
}
// #2: Define how to launch external component
var captureSuccess by remember { mutableStateOf(false) }
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.TakePicture()
) { captureSuccess = it }
// #3: Show UI
Box(modifier, contentAlignment = Alignment.Center) {
if (captureSuccess) {
AsyncImage(uri, contentDescription = null)
} else {
Button({ launcher.launch(uri) }) {
Text("Take picture")
}
}
}
}
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Composable
fun RecordVideo_WithCameraApp(modifier: Modifier = Modifier) {
// #1: Set the location where the image will be stored
val context = LocalContext.current
val uri = remember {
FileProvider.getUriForFile(
context, context.applicationContext.packageName + ".provider",
File(context.filesDir, "video.MP4")
)
}
// #2: Define how to launch external component
var captureSuccess by remember { mutableStateOf(false) }
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.CaptureVideo()
) { captureSuccess = it }
// #3: Show UI
Box(modifier, contentAlignment = Alignment.Center) {
if (captureSuccess) {
Text("Successfully saved video")
} else {
Button({ launcher.launch(uri) }) {
Text("Record video")
}
}
}
}
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
// Context: https://developers.google.com/ml-kit/vision/doc-scanner/android
@Composable
fun ExternalCodeScanner(modifier: Modifier = Modifier) {
// #1: Create a configured scanning client
val context = LocalContext.current
val scanner = remember {
val options = GmsBarcodeScannerOptions.Builder()
.enableAutoZoom()
.allowManualInput()
.setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
.build()
GmsBarcodeScanning.getClient(context, options)
}
// #2: Define how to launch external component
var barcode by remember { mutableStateOf<Barcode?>(null) }
val launchAction: () -> Unit = {
scanner.startScan()
.addOnSuccessListener { barcode = it }
.addOnCanceledListener { /* Deal with cancellation */ }
.addOnFailureListener { /* Deal with failure */ }
}
// #3: Show UI
val barcodeValue = barcode?.rawValue
if (barcodeValue != null) {
Text("Scanned: $barcodeValue", modifier)
} else {
Button(onClick = launchAction, modifier) {
Text("Start code scan!")
}
}
}
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
// Context: https://developers.google.com/ml-kit/vision/doc-scanner/android
@Composable
fun ScanDocumentScreen(modifier: Modifier = Modifier) {
// #1: Create a configured scanning client
val scanner = remember {
val options = GmsDocumentScannerOptions.Builder()
.setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF)
.setScannerMode(SCANNER_MODE_FULL)
.setPageLimit(5)
.setGalleryImportAllowed(true)
.build()
GmsDocumentScanning.getClient(options)
}
// #2: Define how to launch external component
var scanResult by remember { mutableStateOf<GmsDocumentScanningResult?>(null) }
val launcher = rememberLauncherForActivityResult(
StartIntentSenderForResult(),
onResult = { result ->
scanResult = GmsDocumentScanningResult
.fromActivityResultIntent(result.data)
}
)
val activity = LocalContext.current.findActivity()
val launchAction: () -> Unit = {
scanner.getStartScanIntent(activity)
.addOnSuccessListener {
launcher.launch(IntentSenderRequest.Builder(it).build())
}
.addOnFailureListener { /* Deal with failure */ }
}
// #3: Show UI
val firstPageImageUri = scanResult?.pages?.first()
val pageCount = scanResult?.pdf?.pageCount
if(firstPageImageUri != null) {
Column(modifier) {
Text("Page count: $pageCount")
AsyncImage(firstPageImageUri, null)
}
} else {
Button(onClick = launchAction, modifier) {
Text("Start scan!")
}
}
}
private fun Context.findActivity(): ComponentActivity {
var context = this
while (context is ContextWrapper) {
if (context is ComponentActivity) return context
context = context.baseContext
}
throw IllegalStateException("Cannot start scanning document without access to Activity")
}
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Module
@InstallIn(ViewModelComponent::class)
class CameraModule {
@Provides
fun provideController(
@ApplicationContext context: Context
) = LifecycleCameraController(context)
}
class PreviewViewModel(
val cameraController: LifecycleCameraController
) : ViewModel() {
private var runningCameraJob: Job? = null
fun startCamera() {
stopCamera()
runningCameraJob = viewModelScope.launch {
try {
cameraController.bindToLifecycle(
CoroutineLifecycleOwner(coroutineContext)
)
awaitCancellation()
} finally { cameraController.unbind() }
}
}
fun stopCamera() = runningCameraJob?.apply { if (isActive) { cancel() } }
}
@Composable
fun PreviewScreen(
modifier: Modifier = Modifier,
viewModel: PreviewViewModel = viewModel()
) {
LifecycleStartEffect(viewModel) {
viewModel.startCamera()
onStopOrDispose {
viewModel.stopCamera()
}
}
AndroidView(
factory = {
PreviewView(it).apply {
this.controller = viewModel.cameraController
}
},
modifier
)
}
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
// Keep in mind this is based on a snapshot and the API surface will likely change
// For a more up-to-date version, check out https://github.com/google/jetpack-camera-app
@Composable
fun PreviewScreen(
modifier: Modifier = Modifier,
viewModel: PreviewViewModel = viewModel()
) {
val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle()
LifecycleStartEffect(viewModel) {
viewModel.startCamera()
onStopOrDispose {
viewModel.stopCamera()
}
}
if (surfaceRequest != null) {
val viewfinderArgs by surfaceRequest.produceViewfinderArgs(implementationMode)
viewfinderArgs?.let { args ->
Viewfinder(
surfaceRequest = args.viewfinderSurfaceRequest,
implementationMode = args.implementationMode,
transformationInfo = args.transformationInfo,
modifier = modifier.fillMaxSize()
)
}
}
}
@Composable
fun SurfaceRequest?.produceViewfinderArgs(
implementationMode: ImplementationMode
): State<ViewfinderArgs?> {
val surfaceRequest = this
return produceState<ViewfinderArgs?>(
initialValue = null,
surfaceRequest
) {
if (surfaceRequest != null) {
val viewfinderSurfaceRequest =
ViewfinderSurfaceRequest
.Builder(surfaceRequest.resolution)
.build()
surfaceRequest.addRequestCancellationListener(Runnable::run) {
viewfinderSurfaceRequest.markSurfaceSafeToRelease()
}
launch(start = CoroutineStart.UNDISPATCHED) {
try {
val surface = viewfinderSurfaceRequest.getSurface()
surfaceRequest.provideSurface(surface, Runnable::run) {
viewfinderSurfaceRequest.markSurfaceSafeToRelease()
}
} finally {
surfaceRequest.willNotProvideSurface()
}
}
val transformationInfos = MutableStateFlow<SurfaceRequest.TransformationInfo?>(null)
surfaceRequest.setTransformationInfoListener(Runnable::run) {
transformationInfos.value = it
}
var snapshotImplementationMode: ImplementationMode? = null
snapshotFlow { implementationMode }
.combine(transformationInfos.filterNotNull()) { implMode, transformInfo ->
Pair(implMode, transformInfo)
}.takeWhile { (implMode, _) ->
val shouldAbort =
snapshotImplementationMode != null && implMode != snapshotImplementationMode
if (shouldAbort) {
surfaceRequest.invalidate()
}
!shouldAbort
}.collectLatest { (implMode, transformInfo) ->
snapshotImplementationMode = implMode
value = ViewfinderArgs(
viewfinderSurfaceRequest,
implMode,
TransformationInfo(
transformInfo.rotationDegrees,
transformInfo.cropRect.left,
transformInfo.cropRect.right,
transformInfo.cropRect.top,
transformInfo.cropRect.bottom,
transformInfo.isMirroring
)
)
}
}
}
}
data class ViewfinderArgs(
val viewfinderSurfaceRequest: ViewfinderSurfaceRequest,
val implementationMode: ImplementationMode,
val transformationInfo: TransformationInfo
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment