package com.example.barokahstore.ui.components import android.Manifest import android.content.pm.PackageManager import android.view.ViewGroup import android.media.AudioManager import android.media.ToneGenerator import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.camera.core.CameraSelector import androidx.camera.core.ExperimentalGetImage import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.common.InputImage import java.util.concurrent.Executors @OptIn(ExperimentalGetImage::class) @Composable fun BarcodeScannerView( onBarcodeScanned: (String) -> Unit, modifier: Modifier = Modifier ) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current // Permission state var hasCameraPermission by remember { mutableStateOf( ContextCompat.checkSelfPermission( context, Manifest.permission.CAMERA ) == PackageManager.PERMISSION_GRANTED ) } val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission(), onResult = { granted -> hasCameraPermission = granted } ) LaunchedEffect(Unit) { if (!hasCameraPermission) { launcher.launch(Manifest.permission.CAMERA) } } if (!hasCameraPermission) { Box( modifier = modifier .fillMaxWidth() .height(150.dp) .background(Color.DarkGray, RoundedCornerShape(12.dp)), contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("Akses kamera dibutuhkan untuk scan barcode", color = Color.White) Spacer(modifier = Modifier.height(12.dp)) Button( onClick = { launcher.launch(Manifest.permission.CAMERA) }, colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4F46E5)) ) { Text("Izinkan Kamera") } } } } else { // Camera preview & analyzer state var lastScannedBarcode by remember { mutableStateOf("") } var lastScannedTimestamp by remember { mutableStateOf(0L) } val toneGenerator = remember { ToneGenerator(AudioManager.STREAM_MUSIC, 100) } val cameraExecutor = remember { Executors.newSingleThreadExecutor() } val barcodeScanner = remember { BarcodeScanning.getClient() } DisposableEffect(Unit) { onDispose { cameraExecutor.shutdown() barcodeScanner.close() toneGenerator.release() } } Box( modifier = modifier .fillMaxWidth() .height(150.dp) .background(Color.Black, RoundedCornerShape(12.dp)) .border(2.dp, Color(0xFF4F46E5), RoundedCornerShape(12.dp)) ) { AndroidView( factory = { ctx -> val previewView = PreviewView(ctx).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) scaleType = PreviewView.ScaleType.FILL_CENTER } val cameraProviderFuture = ProcessCameraProvider.getInstance(ctx) cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() val preview = Preview.Builder().build().apply { setSurfaceProvider(previewView.surfaceProvider) } val imageAnalysis = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy -> val mediaImage = imageProxy.image if (mediaImage != null) { val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) barcodeScanner.process(image) .addOnSuccessListener { barcodes -> for (barcode in barcodes) { val rawValue = barcode.rawValue ?: continue val currentTime = System.currentTimeMillis() if (rawValue == lastScannedBarcode && (currentTime - lastScannedTimestamp) < 3000) { // Ignore scans within 3 seconds of the same barcode continue } // Update last scan state lastScannedBarcode = rawValue lastScannedTimestamp = currentTime // Beep sound try { toneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP, 150) } catch (e: Exception) { e.printStackTrace() } // Call callback onBarcodeScanned(rawValue) } } .addOnFailureListener { it.printStackTrace() } .addOnCompleteListener { imageProxy.close() } } else { imageProxy.close() } } val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( lifecycleOwner, cameraSelector, preview, imageAnalysis ) } catch (e: Exception) { e.printStackTrace() } }, ContextCompat.getMainExecutor(ctx)) previewView }, modifier = Modifier.fillMaxSize() ) } } }