Añadida la clase conexion y la apertura del lector de codigos.
This commit is contained in:
Generated
+11
@@ -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>
|
||||
Generated
+10
@@ -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>
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -8,6 +8,9 @@ lifecycleRuntimeKtx = "2.6.1"
|
||||
activityCompose = "1.8.0"
|
||||
kotlin = "2.2.10"
|
||||
composeBom = "2026.02.01"
|
||||
cameraX = "1.6.1"
|
||||
mlkitBarcode = "17.3.0"
|
||||
mariadbDriver = "3.5.1"
|
||||
|
||||
[libraries]
|
||||
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-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
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]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
Reference in New Issue
Block a user