From 848d3b47d1874ccebc3e2aa872729ba9d0546ced Mon Sep 17 00:00:00 2001 From: Pedro Javier Date: Wed, 24 Jun 2026 11:49:46 +0200 Subject: [PATCH] Inicio de proyecto --- .gitignore | 15 + .idea/.gitignore | 3 + .idea/AndroidProjectSystem.xml | 6 + .idea/codeStyles/Project.xml | 123 +++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/compiler.xml | 6 + .idea/deploymentTargetSelector.xml | 11 + .idea/gradle.xml | 18 ++ .idea/misc.xml | 10 + .idea/runConfigurations.xml | 17 + .idea/vcs.xml | 6 + .kotlin/errors/errors-1782202378125.log | 50 +++ app/.gitignore | 1 + app/build.gradle.kts | 68 ++++ .../kilometrapp/ExampleInstrumentedTest.kt | 24 ++ app/src/main/AndroidManifest.xml | 35 ++ app/src/main/ic_launcher-playstore.png | Bin 0 -> 25227 bytes .../com/example/kilometrapp/MainActivity.kt | 305 ++++++++++++++++++ app/src/main/keepRules/rules.keep | 12 + app/src/main/res/drawable/focus_frame.xml | 8 + .../res/drawable/ic_launcher_background.xml | 74 +++++ .../res/drawable/ic_launcher_foreground.xml | 15 + app/src/main/res/layout/activity_main.xml | 86 +++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 2290 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 3480 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1684 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2090 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 3230 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 4748 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 4760 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 7128 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 6198 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 9960 bytes app/src/main/res/values/colors.xml | 10 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 14 + app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 ++ .../example/kilometrapp/ExampleUnitTest.kt | 17 + build.gradle.kts | 5 + gradle.properties | 19 ++ gradle/gradle-daemon-jvm.properties | 12 + gradle/libs.versions.toml | 31 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 251 ++++++++++++++ gradlew.bat | 94 ++++++ settings.gradle.kts | 20 ++ 50 files changed, 1424 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/AndroidProjectSystem.xml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/deploymentTargetSelector.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 .kotlin/errors/errors-1782202378125.log create mode 100644 app/.gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/src/androidTest/java/com/example/kilometrapp/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/example/kilometrapp/MainActivity.kt create mode 100644 app/src/main/keepRules/rules.keep create mode 100644 app/src/main/res/drawable/focus_frame.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 app/src/test/java/com/example/kilometrapp/ExampleUnitTest.kt create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/gradle-daemon-jvm.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..7643783 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..ca16a99 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..02c4aa5 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..74dd639 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.kotlin/errors/errors-1782202378125.log b/.kotlin/errors/errors-1782202378125.log new file mode 100644 index 0000000..d3d9689 --- /dev/null +++ b/.kotlin/errors/errors-1782202378125.log @@ -0,0 +1,50 @@ +kotlin version: 2.2.10 +error message: androidx.compose.compiler.plugins.kotlin.IncompatibleComposeRuntimeVersionException: The Compose Compiler requires the Compose Runtime to be on the class path, but none could be found. The compose compiler plugin you are using (version 1.5.14) expects a minimum runtime version of 1.0.0. + at androidx.compose.compiler.plugins.kotlin.VersionChecker.noRuntimeOnClasspathError(VersionChecker.kt:221) + at androidx.compose.compiler.plugins.kotlin.VersionChecker.check(VersionChecker.kt:193) + at androidx.compose.compiler.plugins.kotlin.ComposeIrGenerationExtension.generate(ComposeIrGenerationExtension.kt:63) + at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.applyIrGenerationExtensions(convertToIr.kt:468) + at org.jetbrains.kotlin.fir.pipeline.Fir2IrPipeline.runActualizationPipeline(convertToIr.kt:245) + at org.jetbrains.kotlin.fir.pipeline.Fir2IrPipeline.convertToIrAndActualize(convertToIr.kt:128) + at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.convertToIrAndActualize(convertToIr.kt:97) + at org.jetbrains.kotlin.fir.pipeline.ConvertToIrKt.convertToIrAndActualize$default(convertToIr.kt:72) + at org.jetbrains.kotlin.cli.jvm.compiler.legacy.pipeline.JvmCompilerPipelineKt.convertToIrAndActualizeForJvm(jvmCompilerPipeline.kt:109) + at org.jetbrains.kotlin.cli.pipeline.jvm.JvmFir2IrPipelinePhase.executePhase(JvmFir2IrPipelinePhase.kt:26) + at org.jetbrains.kotlin.cli.pipeline.jvm.JvmFir2IrPipelinePhase.executePhase(JvmFir2IrPipelinePhase.kt:17) + at org.jetbrains.kotlin.cli.pipeline.PipelinePhase.phaseBody(PipelinePhase.kt:68) + at org.jetbrains.kotlin.cli.pipeline.PipelinePhase.phaseBody(PipelinePhase.kt:58) + at org.jetbrains.kotlin.config.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:102) + at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:22) + at org.jetbrains.kotlin.config.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:53) + at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.runPhasedPipeline(AbstractCliPipeline.kt:109) + at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.execute(AbstractCliPipeline.kt:68) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:78) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:44) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:90) + at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:352) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunnerBase.runCompiler(IncrementalJvmCompilerRunnerBase.kt:175) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunnerBase.runCompiler(IncrementalJvmCompilerRunnerBase.kt:38) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:504) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:421) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:306) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:133) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:679) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:93) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1806) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) + + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..706456c --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,68 @@ +plugins { + // Solo dejamos el plugin de la aplicación Android base + alias(libs.plugins.android.application) + // El de Kotlin estándar se aplica automáticamente con el anterior en versiones modernas +} + +android { + namespace = "com.example.kilometrapp" + compileSdk = 35 + + defaultConfig { + applicationId = "com.example.kilometrapp" + minSdk = 26 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + // Soporte para interfaces de usuario y vistas XML clásicas + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("com.google.android.material:material:1.11.0") + implementation(libs.androidx.core.ktx) + + // Librerías de la cámara (CameraX) + implementation("androidx.camera:camera-camera2:1.4.0") + implementation("androidx.camera:camera-lifecycle:1.4.0") + implementation("androidx.camera:camera-view:1.4.0") + + // OCR mediante Google Play Services para tu Realme GT + implementation("com.google.android.gms:play-services-mlkit-text-recognition:19.0.1") + + // Conector MySQL (Mucho más estable en Android para bases de datos MariaDB/MySQL) + implementation("mysql:mysql-connector-java:5.1.49") + + // LINEA CRUCIAL: Añade esto para evitar que la cámara cierre la app al abrirse + implementation("com.google.guava:guava:33.0.0-android") + + // Dependencias básicas del sistema + implementation(libs.androidx.lifecycle.runtime.ktx) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/app/src/androidTest/java/com/example/kilometrapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/kilometrapp/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..0b9b695 --- /dev/null +++ b/app/src/androidTest/java/com/example/kilometrapp/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.kilometrapp + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.kilometrapp", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1bfd5b1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..9a73d4a78bedc06a2fd7771f334e700a67108c27 GIT binary patch literal 25227 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%0AP|gcvmDnGP*_66TG&dk6f<6{iTG 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/example/kilometrapp/MainActivity.kt b/app/src/main/java/com/example/kilometrapp/MainActivity.kt new file mode 100644 index 0000000..684c6f2 --- /dev/null +++ b/app/src/main/java/com/example/kilometrapp/MainActivity.kt @@ -0,0 +1,305 @@ +package com.example.kilometrapp // <-- Asegúrate de que este es tu paquete exacto + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.Rect +import android.view.ScaleGestureDetector +import android.os.Bundle +import android.util.Log +import android.widget.Button +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.OptIn +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.core.* +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.content.ContextCompat +import com.google.mlkit.vision.common.InputImage +import com.google.mlkit.vision.text.Text +import com.google.mlkit.vision.text.TextRecognition +import com.google.mlkit.vision.text.latin.TextRecognizerOptions +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class MainActivity : AppCompatActivity() { + + private lateinit var viewFinder: PreviewView + private lateinit var overlayFrame: android.view.View + private lateinit var tvResult: TextView + private lateinit var btnCapture: Button + private lateinit var cameraExecutor: ExecutorService + private var imageCapture: ImageCapture? = null + private var cameraControl: CameraControl? = null + private var currentZoomRatio = 1f + private var isDialogShowing = false + + // Inicializa el OCR con las opciones estándar + private val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) + + // Lanzador para solicitar el permiso de forma nativa en pantalla + private val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + viewFinder.post { startCamera() } + } else { + Toast.makeText(this, "Permiso de cámara obligatorio denegado.", Toast.LENGTH_LONG).show() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + try { + setContentView(R.layout.activity_main) + + viewFinder = findViewById(R.id.viewFinder) + overlayFrame = findViewById(R.id.overlayFrame) + tvResult = findViewById(R.id.tvResult) + btnCapture = findViewById(R.id.btnCapture) + + cameraExecutor = Executors.newSingleThreadExecutor() + + // Configurar el detector de gestos de zoom + val scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector): Boolean { + cameraControl?.let { control -> + val scale = detector.scaleFactor + currentZoomRatio *= scale + // Limitamos el zoom entre 1x y el máximo de la cámara (ej: 8x) + currentZoomRatio = currentZoomRatio.coerceIn(1f, 8f) + control.setZoomRatio(currentZoomRatio) + } + return true + } + }) + + // Aplicar el detector al visor de la cámara + viewFinder.setOnTouchListener { view, event -> + scaleGestureDetector.onTouchEvent(event) + view.performClick() + true + } + + btnCapture.setOnClickListener { takePhoto() } + + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + viewFinder.post { startCamera() } + } else { + requestPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } catch (e: Exception) { + Log.e("Kilometrapp", "Error controlado en UI: ${e.message}") + Toast.makeText(this, "Error en la interfaz: ${e.message}", Toast.LENGTH_LONG).show() + } + } + + private fun startCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(this) + + cameraProviderFuture.addListener({ + try { + val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() + + val preview = Preview.Builder() + .build() + .also { + it.surfaceProvider = viewFinder.surfaceProvider + } + + imageCapture = ImageCapture.Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) + .build() + + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + cameraProvider.unbindAll() + + val camera = cameraProvider.bindToLifecycle( + this, cameraSelector, preview, imageCapture + ) + + cameraControl = camera.cameraControl + + // Opcional: Establecer un zoom inicial (ej: 2x) para ver mejor el tablero + // cameraControl?.setLinearZoom(0.3f) // 0.0 a 1.0 + + Log.d("Kilometrapp", "Cámara vinculada correctamente para captura") + + } catch (exc: Exception) { + Log.e("Kilometrapp", "Error al inicializar CameraX", exc) + Toast.makeText(this, "Error al abrir la cámara", Toast.LENGTH_SHORT).show() + } + }, ContextCompat.getMainExecutor(this)) + } + + private fun takePhoto() { + val imageCapture = imageCapture ?: return + + imageCapture.takePicture( + ContextCompat.getMainExecutor(this), + object : ImageCapture.OnImageCapturedCallback() { + override fun onCaptureSuccess(imageProxy: ImageProxy) { + processStaticImage(imageProxy) + } + + override fun onError(exception: ImageCaptureException) { + Log.e("Kilometrapp", "Error al capturar foto: ${exception.message}") + } + } + ) + } + + @OptIn(ExperimentalGetImage::class) + private fun processStaticImage(imageProxy: ImageProxy) { + val mediaImage = imageProxy.image + if (mediaImage != null) { + val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) + + recognizer.process(image) + .addOnSuccessListener { visionText -> + val bloquesEnCuadro = visionText.textBlocks.filter { + isInsideFocusFrame(it.boundingBox, imageProxy.width, imageProxy.height) + } + + if (bloquesEnCuadro.isEmpty()) { + Toast.makeText(this, "No se detectó texto en el recuadro", Toast.LENGTH_SHORT).show() + } else { + showSelectionDialog(bloquesEnCuadro) + } + } + .addOnCompleteListener { + imageProxy.close() + } + } else { + imageProxy.close() + } + } + + private fun showSelectionDialog(bloques: List) { + val candidatos = mutableListOf() + + for (bloque in bloques) { + // Buscamos cualquier secuencia de números (de 2 a 8 cifras) + // Esto separará el "22" del "246115" aunque el OCR los pegue + val matches = Regex("\\d{2,8}").findAll(bloque.text) + matches.forEach { match -> + val num = match.value + // Si el número es muy largo (ej: 22246115), intentamos ver si los últimos 6 son el kilometraje + if (num.length >= 7) { + val ultimosSeis = num.takeLast(6) + if (!candidatos.contains(ultimosSeis)) candidatos.add(ultimosSeis) + + val primerosDos = num.take(2) + if (!candidatos.contains(primerosDos)) candidatos.add(primerosDos) + } else { + if (!candidatos.contains(num)) candidatos.add(num) + } + } + } + + if (candidatos.isEmpty()) { + Toast.makeText(this, "No se encontraron números claros", Toast.LENGTH_SHORT).show() + return + } + + // Ordenamos para que los números de 6 cifras (kilometraje habitual) salgan arriba + val listaFinal = candidatos.sortedByDescending { it.length }.toTypedArray() + + AlertDialog.Builder(this) + .setTitle("Selecciona el Kilometraje") + .setItems(listaFinal) { _, which -> + val seleccionado = listaFinal[which] + tvResult.text = "$seleccionado KM" + showConfirmationDialog(seleccionado) + } + .setNegativeButton("Cancelar", null) + .show() + } + + private fun showConfirmationDialog(kilometraje: String) { + val opciones = arrayOf("Revisión kilómetros", "Cambio de aceite") + + AlertDialog.Builder(this) + .setTitle("¿Qué quieres registrar?") + .setMessage("Kilometraje: $kilometraje KM") + .setPositiveButton("Revisión") { _, _ -> + enviarAServidor(kilometraje, "Revision kilometros") + } + .setNeutralButton("Cambio Aceite") { _, _ -> + enviarAServidor(kilometraje, "Cambio de aceite") + } + .setNegativeButton("Reintentar", null) + .show() + } + + private fun enviarAServidor(kilometraje: String, observacionesText: String) { + cameraExecutor.execute { + try { + // Usamos el driver de MySQL que es el más estable en Android + Class.forName("com.mysql.jdbc.Driver") + + // Datos de conexión + val url = "jdbc:mysql://mariadb.peta9.com:3306/mantenimiento_db" + val user = "pedro" + val pass = "Pedro@110387" + + val connection = java.sql.DriverManager.getConnection(url, user, pass) + + // Preparar el INSERT (corregido: comas y parámetros) + val sql = "INSERT INTO mantenimiento (kms, observaciones, fecha, foto) VALUES (?, ?, NOW(), null)" + val statement = connection.prepareStatement(sql) + + // Extraemos solo los números para guardar como entero + val soloNumeros = kilometraje.filter { it.isDigit() }.toIntOrNull() ?: 0 + + statement.setInt(1, soloNumeros) + statement.setString(2, observacionesText) + + statement.executeUpdate() + connection.close() + + runOnUiThread { + Toast.makeText(this, "¡$observacionesText guardado!", Toast.LENGTH_SHORT).show() + } + } catch (e: Throwable) { + Log.e("Kilometrapp", "Error MariaDB: ${e.message}") + runOnUiThread { + Toast.makeText(this, "Error de conexión: ${e.message}", Toast.LENGTH_LONG).show() + } + } + } + } + + private fun isInsideFocusFrame(boundingBox: Rect?, imgWidth: Int, imgHeight: Int): Boolean { + if (boundingBox == null) return false + + // 1. Obtener dimensiones del visor y del recuadro en pantalla + val viewWidth = viewFinder.width + val viewHeight = viewFinder.height + + // 2. Coordenadas del recuadro de enfoque en la pantalla + val frameLeft = overlayFrame.left.toFloat() + val frameTop = overlayFrame.top.toFloat() + val frameRight = overlayFrame.right.toFloat() + val frameBottom = overlayFrame.bottom.toFloat() + + // 3. Mapear coordenadas del OCR (que vienen en tamaño de imagen) a coordenadas de pantalla + // Nota: Esto es una simplificación, pero para un recuadro central funciona bien. + val scaleX = viewWidth.toFloat() / imgHeight.toFloat() // CameraX suele invertir W/H por rotación + val scaleY = viewHeight.toFloat() / imgWidth.toFloat() + + val rectCenterX = boundingBox.centerX() * scaleX + val rectCenterY = boundingBox.centerY() * scaleY + + // 4. Verificar si el centro del texto detectado está dentro del rectángulo rojo + return rectCenterX in frameLeft..frameRight && rectCenterY in frameTop..frameBottom + } + + override fun onDestroy() { + super.onDestroy() + cameraExecutor.shutdown() + } +} diff --git a/app/src/main/keepRules/rules.keep b/app/src/main/keepRules/rules.keep new file mode 100644 index 0000000..d7e081a --- /dev/null +++ b/app/src/main/keepRules/rules.keep @@ -0,0 +1,12 @@ +# Add project specific R8 rules here. +# AGP will combine all keep rule files in src/main/keepRules to pass to R8 +# +# For more details, see +# https://d.android.com/r/tools/r8/keep-rules + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} \ No newline at end of file diff --git a/app/src/main/res/drawable/focus_frame.xml b/app/src/main/res/drawable/focus_frame.xml new file mode 100644 index 0000000..7088223 --- /dev/null +++ b/app/src/main/res/drawable/focus_frame.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..eb83232 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b01e349 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + +