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
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PlanningModeManager">
<option name="approvalStates">
<map>
<entry key="0ddb545e-9913-4e63-9012-965c11ed4f60" value="false" />
</map>
</option>
</component>
</project>
+64
View File
@@ -0,0 +1,64 @@
# SAIpp - Barcode Scanner & MariaDB Connector
Android application developed in Kotlin using Jetpack Compose that scans barcodes and saves them directly to a self-hosted MariaDB database.
## 🚀 Features
- **Instant Scan**: The camera opens automatically upon application startup.
- **ML Kit Integration**: High-performance barcode detection using Google ML Kit.
- **MariaDB Connectivity**: Direct connection to a remote database via JDBC.
- **Modern UI**: Built with Jetpack Compose and Material 3.
- **Permission Handling**: Automatic runtime camera permission requests.
## 🛠️ Requirements
- Android device with API 34+ (Android 14+).
- MariaDB Server accessible from the mobile device network.
- Camera hardware.
## ⚙️ Configuration
### 1. Database Setup
Ensure your MariaDB database has a table named `scans`. You can use the following SQL:
```sql
CREATE TABLE scans (
id INT AUTO_INCREMENT PRIMARY KEY,
barcode VARCHAR(255) NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
### 2. App Credentials
Update your database connection details in:
`app/src/main/java/com/example/saipp/data/MariaDbConnection.kt`
```kotlin
private const val HOST = "YOUR_SERVER_IP"
private const val DATABASE = "YOUR_DB_NAME"
private const val USER = "YOUR_USER"
private const val PASSWORD = "YOUR_PASSWORD"
```
### 3. Network Access
- Ensure the MariaDB port (default **3306**) is open in your firewall.
- Your MariaDB user must be allowed to connect from remote IPs:
```sql
GRANT ALL PRIVILEGES ON YOUR_DB_NAME.* TO 'YOUR_USER'@'%' IDENTIFIED BY 'YOUR_PASSWORD';
FLUSH PRIVILEGES;
```
## 📦 Dependencies
- **CameraX**: For camera preview and frame capture.
- **ML Kit Barcode Scanning**: For processing barcodes.
- **MariaDB Java Client**: JDBC driver for database communication.
- **Coroutines**: For asynchronous database operations.
## ⚠️ Security Warning
> [!CAUTION]
> This project uses a **direct JDBC connection** from the mobile app to a remote database. This is suitable for personal projects or internal tools, but is **not recommended for production apps** exposed to the public, as it requires storing database credentials in the code and exposing database ports to the internet.
## 📄 License
This project is for educational/personal use.
+19
View File
@@ -35,6 +35,11 @@ android {
buildFeatures { buildFeatures {
compose = true compose = true
} }
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
} }
dependencies { dependencies {
@@ -46,6 +51,20 @@ dependencies {
implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.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) testImplementation(libs.junit)
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4) androidTestImplementation(libs.androidx.compose.ui.test.junit4)
+4
View File
@@ -2,6 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> 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 <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@@ -1,17 +1,35 @@
package com.example.saipp package com.example.saipp
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge 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.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.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 com.example.saipp.ui.theme.SAIppTheme
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -19,29 +37,78 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
SAIppTheme { SAIppTheme {
MainScreen()
}
}
}
}
@Composable
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 -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting( Box(
name = "Android", modifier = Modifier
modifier = Modifier.padding(innerPadding) .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 {
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text( Text(
text = "Hello $name!", text = "Se requiere permiso de cámara para escanear.",
modifier = modifier modifier = Modifier.align(Alignment.Center)
) )
} }
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
SAIppTheme {
Greeting("Android")
} }
} }
@@ -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))
}
)
}
+9
View File
@@ -8,6 +8,9 @@ lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0" activityCompose = "1.8.0"
kotlin = "2.2.10" kotlin = "2.2.10"
composeBom = "2026.02.01" composeBom = "2026.02.01"
cameraX = "1.6.1"
mlkitBarcode = "17.3.0"
mariadbDriver = "3.5.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -24,6 +27,12 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "cameraX" }
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "cameraX" }
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "cameraX" }
androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "cameraX" }
mlkit-barcode-scanning = { group = "com.google.mlkit", name = "barcode-scanning", version.ref = "mlkitBarcode" }
mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version.ref = "mariadbDriver" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }