Añadir la lectura de codigo de barras
This commit is contained in:
@@ -3,6 +3,7 @@ package com.example.saipp
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
@@ -12,9 +13,12 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -71,6 +75,9 @@ fun MainScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
var scannedBarcode by remember { mutableStateOf<String?>(null) }
|
||||
var isSaving by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -78,27 +85,15 @@ fun MainScreen() {
|
||||
.padding(innerPadding)
|
||||
) {
|
||||
if (hasCameraPermission) {
|
||||
var isProcessing by remember { mutableStateOf(false) }
|
||||
|
||||
BarcodeScannerView(onBarcodeDetected = { barcode ->
|
||||
if (!isProcessing) {
|
||||
isProcessing = true
|
||||
scope.launch {
|
||||
val result = repository.saveBarcode(barcode)
|
||||
result.onSuccess {
|
||||
Toast.makeText(context, "Guardado: $barcode", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
result.onFailure { e ->
|
||||
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
// Add a small delay to avoid multiple triggers for the same barcode
|
||||
kotlinx.coroutines.delay(2000)
|
||||
isProcessing = false
|
||||
}
|
||||
}
|
||||
})
|
||||
// Only scan if no barcode is currently being shown in the popup
|
||||
if (scannedBarcode == null) {
|
||||
BarcodeScannerView(onBarcodeDetected = { barcode ->
|
||||
Log.d("MainActivity", "Barcode detected: $barcode")
|
||||
scannedBarcode = barcode
|
||||
})
|
||||
}
|
||||
|
||||
if (isProcessing) {
|
||||
if (isSaving) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
@@ -109,6 +104,51 @@ fun MainScreen() {
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
|
||||
// Popup Dialog for scanned results
|
||||
scannedBarcode?.let { barcode ->
|
||||
ResultDialog(
|
||||
barcode = barcode,
|
||||
onDismiss = { scannedBarcode = null },
|
||||
onConfirm = {
|
||||
scannedBarcode = null
|
||||
isSaving = true
|
||||
scope.launch {
|
||||
val result = repository.saveBarcode(barcode)
|
||||
isSaving = false
|
||||
result.onSuccess {
|
||||
Toast.makeText(context, "Guardado con éxito", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
result.onFailure { e ->
|
||||
Toast.makeText(context, "Error al guardar: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResultDialog(
|
||||
barcode: String,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(text = "Código Detectado") },
|
||||
text = { Text(text = "Se ha leído el código:\n\n$barcode\n\n¿Deseas guardarlo en la base de datos?") },
|
||||
confirmButton = {
|
||||
Button(onClick = onConfirm) {
|
||||
Text("Guardar")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("Cerrar")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.example.saipp.ui.scanner
|
||||
|
||||
import android.util.Log
|
||||
import android.util.Size
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.camera.core.CameraSelector
|
||||
@@ -9,15 +10,32 @@ import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.RoundRect
|
||||
import androidx.compose.ui.graphics.ClipOp
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.clipPath
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@@ -29,75 +47,150 @@ fun BarcodeScannerView(
|
||||
val context = LocalContext.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val cameraExecutor = remember { Executors.newSingleThreadExecutor() }
|
||||
val barcodeScanner = remember { BarcodeScanning.getClient() }
|
||||
|
||||
// Use updated state to ensure the analyzer always has the latest callback
|
||||
val currentOnBarcodeDetected by rememberUpdatedState(onBarcodeDetected)
|
||||
|
||||
AndroidView(
|
||||
factory = { ctx ->
|
||||
PreviewView(ctx).apply {
|
||||
scaleType = PreviewView.ScaleType.FILL_CENTER
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
val options = BarcodeScannerOptions.Builder()
|
||||
.setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
|
||||
.build()
|
||||
val barcodeScanner = remember { BarcodeScanning.getClient(options) }
|
||||
|
||||
val previewView = remember {
|
||||
PreviewView(context).apply {
|
||||
scaleType = PreviewView.ScaleType.FILL_CENTER
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup resources when the composable is disposed
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
barcodeScanner.close()
|
||||
cameraExecutor.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(lifecycleOwner) {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider = cameraProviderFuture.get()
|
||||
|
||||
val preview = Preview.Builder().build().also {
|
||||
it.setSurfaceProvider(previewView.surfaceProvider)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
update = { previewView ->
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
||||
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider = cameraProviderFuture.get()
|
||||
val imageAnalysis = ImageAnalysis.Builder()
|
||||
.setTargetResolution(Size(1280, 720)) // HD resolution for better detection
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
.also { analysis ->
|
||||
var lastLogTime = 0L
|
||||
analysis.setAnalyzer(cameraExecutor) { imageProxy ->
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (currentTime - lastLogTime > 2000) { // Log every 2 seconds
|
||||
Log.d("BarcodeScanner", "Analyzer: Processing frame. Rotation: ${imageProxy.imageInfo.rotationDegrees}")
|
||||
lastLogTime = currentTime
|
||||
}
|
||||
|
||||
val preview = Preview.Builder().build().also {
|
||||
it.setSurfaceProvider(previewView.surfaceProvider)
|
||||
}
|
||||
val mediaImage = imageProxy.image
|
||||
if (mediaImage != null) {
|
||||
val image = InputImage.fromMediaImage(
|
||||
mediaImage,
|
||||
imageProxy.imageInfo.rotationDegrees
|
||||
)
|
||||
|
||||
val imageAnalysis = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
.also { analysis ->
|
||||
analysis.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) {
|
||||
barcode.rawValue?.let { value ->
|
||||
onBarcodeDetected(value)
|
||||
}
|
||||
barcodeScanner.process(image)
|
||||
.addOnSuccessListener { barcodes ->
|
||||
for (barcode in barcodes) {
|
||||
barcode.rawValue?.let { value ->
|
||||
Log.d("BarcodeScanner", "Barcode detected: $value")
|
||||
currentOnBarcodeDetected(value)
|
||||
}
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
Log.e("BarcodeScanner", "Scanning failed", e)
|
||||
}
|
||||
.addOnCompleteListener {
|
||||
imageProxy.close()
|
||||
}
|
||||
} else {
|
||||
imageProxy.close()
|
||||
}
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
Log.e("BarcodeScanner", "Scanning failed", e)
|
||||
}
|
||||
.addOnCompleteListener {
|
||||
imageProxy.close()
|
||||
}
|
||||
} else {
|
||||
imageProxy.close()
|
||||
}
|
||||
}
|
||||
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
|
||||
try {
|
||||
cameraProvider.unbindAll()
|
||||
cameraProvider.bindToLifecycle(
|
||||
lifecycleOwner,
|
||||
cameraSelector,
|
||||
preview,
|
||||
imageAnalysis
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("BarcodeScanner", "Use case binding failed", e)
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(context))
|
||||
|
||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
|
||||
try {
|
||||
cameraProvider.unbindAll()
|
||||
cameraProvider.bindToLifecycle(
|
||||
lifecycleOwner,
|
||||
cameraSelector,
|
||||
preview,
|
||||
imageAnalysis
|
||||
)
|
||||
Log.d("BarcodeScanner", "Camera and Analysis bound successfully")
|
||||
} catch (e: Exception) {
|
||||
Log.e("BarcodeScanner", "Use case binding failed", e)
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(context))
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
AndroidView(
|
||||
factory = { previewView },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
|
||||
ScannerOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScannerOverlay() {
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
val canvasWidth = size.width
|
||||
val canvasHeight = size.height
|
||||
val boxWidth = canvasWidth * 0.7f
|
||||
val boxHeight = boxWidth
|
||||
val left = (canvasWidth - boxWidth) / 2
|
||||
val top = (canvasHeight - boxHeight) / 2
|
||||
|
||||
val rectPath = Path().apply {
|
||||
addRoundRect(
|
||||
RoundRect(
|
||||
rect = androidx.compose.ui.geometry.Rect(
|
||||
offset = Offset(left, top),
|
||||
size = androidx.compose.ui.geometry.Size(boxWidth, boxHeight)
|
||||
),
|
||||
cornerRadius = CornerRadius(16.dp.toPx())
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
clipPath(rectPath, clipOp = ClipOp.Difference) {
|
||||
drawRect(color = Color.Black.copy(alpha = 0.5f))
|
||||
}
|
||||
|
||||
drawRoundRect(
|
||||
color = Color.White,
|
||||
topLeft = Offset(left, top),
|
||||
size = androidx.compose.ui.geometry.Size(boxWidth, boxHeight),
|
||||
cornerRadius = CornerRadius(16.dp.toPx()),
|
||||
style = Stroke(width = 2.dp.toPx())
|
||||
)
|
||||
|
||||
// Back to horizontal line as it's more effective for 1D barcodes
|
||||
drawLine(
|
||||
color = Color.Red,
|
||||
start = Offset(left + 20.dp.toPx(), canvasHeight / 2),
|
||||
end = Offset(left + boxWidth - 20.dp.toPx(), canvasHeight / 2),
|
||||
strokeWidth = 2.dp.toPx()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user