Añadida la clase conexion y la apertura del lector de codigos.

This commit is contained in:
2026-07-30 09:31:28 +02:00
parent 4e3202f86f
commit e6497704df
10 changed files with 387 additions and 20 deletions
+19
View File
@@ -35,6 +35,11 @@ android {
buildFeatures {
compose = true
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
@@ -46,6 +51,20 @@ dependencies {
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
// CameraX
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
// ML Kit Barcode Scanning
implementation(libs.mlkit.barcode.scanning)
// MariaDB JDBC Driver
implementation(libs.mariadb.java.client)
testImplementation(libs.junit)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
+4
View File
@@ -2,6 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -1,17 +1,35 @@
package com.example.saipp
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
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.CircularProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import com.example.saipp.data.ScannerRepository
import com.example.saipp.ui.scanner.BarcodeScannerView
import com.example.saipp.ui.theme.SAIppTheme
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -19,29 +37,78 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
setContent {
SAIppTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
MainScreen()
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
SAIppTheme {
Greeting("Android")
fun MainScreen() {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val repository = remember { ScannerRepository() }
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(key1 = true) {
if (!hasCameraPermission) {
launcher.launch(Manifest.permission.CAMERA)
}
}
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Box(
modifier = Modifier
.fillMaxSize()
.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
}
}
})
if (isProcessing) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
} else {
Text(
text = "Se requiere permiso de cámara para escanear.",
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
@@ -0,0 +1,37 @@
package com.example.saipp.data
import android.util.Log
import java.sql.Connection
import java.sql.DriverManager
import java.sql.SQLException
import java.util.Properties
object MariaDbConnection {
private const val TAG = "MariaDbConnection"
// TODO: Replace with your actual database details
private const val HOST = "YOUR_HOST_IP"
private const val PORT = "3306"
private const val DATABASE = "YOUR_DATABASE_NAME"
private const val USER = "YOUR_USER"
private const val PASSWORD = "YOUR_PASSWORD"
private const val URL = "jdbc:mariadb://$HOST:$PORT/$DATABASE"
fun getConnection(): Connection? {
return try {
// Load the driver explicitly if needed
Class.forName("org.mariadb.jdbc.Driver")
val props = Properties()
props.setProperty("user", USER)
props.setProperty("password", PASSWORD)
props.setProperty("connectTimeout", "5000") // 5 seconds timeout
DriverManager.getConnection(URL, props)
} catch (e: Exception) {
Log.e(TAG, "Error connecting to MariaDB: ${e.message}", e)
null
}
}
}
@@ -0,0 +1,43 @@
package com.example.saipp.data
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.sql.SQLException
class ScannerRepository {
companion object {
private const val TAG = "ScannerRepository"
}
suspend fun saveBarcode(barcode: String): Result<Unit> = withContext(Dispatchers.IO) {
val connection = MariaDbConnection.getConnection()
if (connection == null) {
return@withContext Result.failure(Exception("Failed to connect to database"))
}
try {
// TODO: Customize table and column names
val sql = "INSERT INTO scans (barcode, timestamp) VALUES (?, CURRENT_TIMESTAMP)"
val statement = connection.prepareStatement(sql)
statement.setString(1, barcode)
val rowsInserted = statement.executeUpdate()
if (rowsInserted > 0) {
Log.d(TAG, "Barcode saved successfully: $barcode")
Result.success(Unit)
} else {
Result.failure(Exception("No rows inserted"))
}
} catch (e: SQLException) {
Log.e(TAG, "Database error: ${e.message}", e)
Result.failure(e)
} finally {
try {
connection.close()
} catch (e: SQLException) {
Log.e(TAG, "Error closing connection: ${e.message}", e)
}
}
}
}
@@ -0,0 +1,103 @@
package com.example.saipp.ui.scanner
import android.util.Log
import android.view.ViewGroup
import androidx.annotation.OptIn
import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.LocalLifecycleOwner
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(
onBarcodeDetected: (String) -> Unit
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val cameraExecutor = remember { Executors.newSingleThreadExecutor() }
val barcodeScanner = remember { BarcodeScanning.getClient() }
AndroidView(
factory = { ctx ->
PreviewView(ctx).apply {
scaleType = PreviewView.ScaleType.FILL_CENTER
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
},
modifier = Modifier.fillMaxSize(),
update = { previewView ->
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
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)
}
}
}
.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))
}
)
}