commit 848d3b47d1874ccebc3e2aa872729ba9d0546ced Author: Pedro Javier Date: Wed Jun 24 11:49:46 2026 +0200 Inicio de proyecto 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 0000000..9a73d4a Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ 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 @@ + + + + + + + + + + + +