# Cámara, galería de imágenes y reconocer texto
## Dependencias
Usaremos el coil y el mlkit para text-recognition por lo que en build.gradle.kts(app) debemos añadir:
```
implementation("io.coil-kt:coil-compose:2.3.0")
implementation("com.google.android.gms:play-services-mlkit-text-recognition:19.0.0")
```
Y usaremos mlkit para reconocer los caracteres en las imágenes. Investiga sobre el módulo:
https://developers.google.com/ml-kit?hl=es-419
Puedes incluso hacer un codela, eso sí, en java:
https://codelabs.developers.google.com/codelabs/mlkit-android#0
También necesitamos ir al manifest y añadir el permiso para usar la cámara:
```
<uses-permission android:name="android.permission.CAMERA"/>
```
Dará un error...arréglalo!
```
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.recognitionapp"
compileSdk = 33
defaultConfig {
applicationId = "com.example.recognitionapp"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.3"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("io.coil-kt:coil-compose:2.3.0")
implementation("com.google.android.gms:play-services-mlkit-text-recognition:19.0.0")
}
```
## Provider
Necesitamos en el manifest además incluir la figura del proveedor o provider que es lo que genera una conexión de nuestra aplicación con los elementos del dispositivo, en este caso, la cámara.
Dentro de application debemos incluir lo que sigue:
```
<provider
android:authorities="${applicationId}.provider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/path_provider"
/>
</provider>
```
Nos dará error porque debemos crear el @xml/path_provider, sigue las instrucciones para la creación automática y llegaremos a esta pantalla:

Como ves es el generador de vistas XML pero vamos a escribir sólo el código:
```
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-cache-path name="my_images" path="/" />
</paths>
```
Este path nos va a permitir la caché de las imágenes que generemos con la cámara.
## TabsView
Vamos a usar un nuevo tipo de navegación por tabs, así que creamos los típicos package: views y viewModel.
En viewModel creamos la clase ScannerViewModel y la dejamos vacía por ahora.
Empezamos con las Views, vamos a crearlas vacías sólo para ver cómo funciona el tab.
Creamos los siguientes files: GalleryView, CameraView, CollectionGalleryView y TabsView, en este último crearemos los tabs pero en los anteriores para simplemente ver cómo funciona pondremos el código siguiente, sustituyendo nombres por los correspondientes:
```
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.anluisa.recognitionapp1.viewModel.ScannerViewModel
@Composable
fun GalleryView(viewModel:ScannerViewModel){
Text(text = "GalleryView")
}
```
En el TabsView programamos la navegación:
```
@Composable
fun TabsView(viewModel: ScannerViewModel){
var selectedTab by remember { mutableStateOf(0) }
val tabs = listOf("Galeria","Camara","Coleccion")
Column {
TabRow(selectedTabIndex = selectedTab,
contentColor = Color.Black,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[selectedTab])
)
}
) {
tabs.forEachIndexed { index, title ->
Tab(selected = selectedTab == index,
onClick = { selectedTab = index },
text = { Text(text = title) }
)
}
}
when(selectedTab){
0 -> GalleryView(viewModel)
1 -> CameraView(viewModel)
2 -> CollectionGalleryView()
}
}
}
```
Modificamos el MainActivity para que podamos ver este nuevo componente. Intenta hacerlo por ti mismo.
## Galería
Vamos a trabajar sobre la galería, para ello lo primero es añadir un recurso drawable, recuerda como llegar a esta pantalla:

Este sería el código:
```
@Composable
fun GalleryView(viewModel: ScannerViewModel) {
val context = LocalContext.current
val clipboard = LocalClipboardManager.current
var image: Any? by remember { mutableStateOf(R.drawable.gallery) }
val photoPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia()
){
if (it != null){
image = it
}else{
//Mostrará algo si hay errores
}
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
modifier = Modifier
.clickable {
photoPicker.launch(
PickVisualMediaRequest(
ActivityResultContracts.PickVisualMedia.ImageOnly
)
)
}
.padding(16.dp, 8.dp),
painter = rememberAsyncImagePainter(image),
contentDescription = null
)
}
}
```
Ahora mismo deberíamos poder ejecutar la aplicación y abrir la Galería.
## TextRecognizer viewModel
El código es bastante sencillo, investiga cómo funciona:
```
class ScannerViewModel:ViewModel() {
var recognizedText by mutableStateOf("")
private set
fun cleanText(){
recognizedText = ""
}
fun onRecognizedText(text: Any?, context: Context){
var image: InputImage? = null
try {
image = InputImage.fromFilePath(context, text as Uri)
} catch (e: IOException){
e.printStackTrace()
}
image?.let {
TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS).process(it)
.addOnSuccessListener { text ->
recognizedText = text.text
}.addOnFailureListener{
showToast(context, "Error al leer la imagen")
}
}
}
fun showToast(context: Context, message : String){
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}
```
## Tomar texto de la imagen
Para poder coger el texto de la imagen debemos modificar el GalleryView como sigue:
```
@Composable
fun GalleryView(viewModel: ScannerViewModel) {
val context = LocalContext.current
val clipboard = LocalClipboardManager.current
var image: Any? by remember { mutableStateOf(R.drawable.gallery) }
val photoPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickVisualMedia()
){
if (it != null){
image = it
viewModel.onRecognizedText(image, context)
}else{
viewModel.showToast(context,"No se ha seleccionado ninguna imagen")
}
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
modifier = Modifier
.clickable {
photoPicker.launch(
PickVisualMediaRequest(
ActivityResultContracts.PickVisualMedia.ImageOnly
)
)
}
.padding(16.dp, 8.dp),
painter = rememberAsyncImagePainter(image),
contentDescription = null
)
Spacer(modifier = Modifier.height(25.dp))
val scrollState = rememberScrollState()
Text(text = viewModel.recognizedText,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
.verticalScroll(scrollState)
.clickable {
clipboard.setText(AnnotatedString(viewModel.recognizedText))
viewModel.showToast(context,"Copiado")
}
)
}
}
```